简体   繁体   中英

How can I export a function from a Svelte component that changes a value in the component?

I have a component called WastedTime.svelte with a value wastedTime . There's also a function to change the value to 50 (in my real code, this does an animation but this is a reduced test case for Stack Overflow).

To allow the child function to be called from a parent, I have used <script context="module"> per the Svelte docs:

<script context="module">
    var wastedTime = 0;
    export function changeNumber(){
        console.log('changing number')
        wastedTime = 50
    }
</script>

<script>
    // Putting 'var wastedTime = 0' here doesn't work either
</script>


<h1>Wasted time: {wastedTime}</h1>

The parent calls the function in the child from onMount :

<script>

    import { onMount } from 'svelte';
    import WastedTime, {changeNumber } from './WastedTime.svelte';

    onMount(() => {
        changeNumber()
    });
</script>

<WastedTime />

The problem is that since wastedTime is referred to in <script context="module"> , it can't seem to change wastedTime . The exported function runs, but wastedTime stays at 0.

Copy of this on Svelte REPL

I have tried: - Putting var wastedTime = 0 in <script context="module"> - Putting var wastedTime = 0 in <script>

Neither works.

How can I export a function from a Svelte component that changes a value in the component?

<script context="module"> isn't reactive — changes to variables inside this block won't affect individual instances (unless you were changing a store value, and every instance was subscribed to that store).

Instead, export the changeNumber function directly from the instance, and get a reference to it with bind:this :

WastedTime.svelte

<script>
    var someNumber = 0;
    export function changeNumber(){
        console.log('changing number')
        someNumber = 56
    }
</script>

<h1>Wasted time: {someNumber}</h1>

App.svelte

<script>
    import { onMount } from 'svelte';
    import WastedTime from './WastedTime.svelte';

    let wastedTimeComponent;

    onMount(() => {
        wastedTimeComponent.changeNumber()
    });
</script>

<WastedTime bind:this={wastedTimeComponent} />

Demo here: https://svelte.dev/repl/f5304fef5c6e43edb8bf0d25d634f965

Simplified version of Rich's answer:

App.svelte

<script>
import Time from './Time.svelte';

let timeComponent;
let time;
</script>

<Time bind:this={timeComponent} bind:time />

<h1>Spent time: {time} ms</h1>

{#if timeComponent}
  <button on:click={() => timeComponent.spendTime() }>Click me</button>
{:else}
  Loading...
{/if}

Time.svelte

<script>
export var time = 0;
export function spendTime() {
  time += 50;
}
</script>

The key here is export function .

Well, that's a damn good question!

My First Attempt:

I've tried @Rich Harris answer (export a function from within the component, bind it when using the component with onMount event listener). The concept is indeed solid, but my problem was a little bit more complex - I've tried to pass an event with parameter to be invoked from the outside. And yes - this parameter is forged using {#await} .. so... no luck with that approach


My Second Attempt:

After learning more about how svelte handles events (I'm a newbie to svelte and learning as I progress), I've found this great article. In short: let's make our component truly-event oriented! In your question - you actually trying to implement onload event... so.. why shouldn't we do just that?

component.svelte

<script>
  import { createEventDispatcher, onMount } from 'svelte';
  const dispatch = createEventDispatcher();

  function doSomething() { console.log('wowza!') }
  
  // wait for onMount to trigger, then dispatch event named "load"
  onMount(() => dispatch('load', 
  { 
    data: 'yay',
    doSomething
  }));
</script>

app.svelte

<script>
  import { Component } from './component.svelte';

  function component_load(event)
  {
    console.log(event.detail.data);
    event.detail.doSomething();
  }
</script>
<Component on:load={component_load} />

So - yep, I've learned something today: Also:

  • This is a more elegant way to code (event-driven)
  • Component expose itself with proper usage of Svelte events life-cycle
  • dispatch can be triggered in response to other events - letting you constuct a complete life-cycle for your component

AMAZING!

A Simpler Solution

Parent component:

<script>
    import Counter from './Counter.svelte'
    let counterElement
</script>

<Counter bind:this={counterElement} />

<button on:click={_=> counterElement.add()}>Add</button>

Child component:

<script>
    export const add =_=> count += 1
    let count = 0
</script>

<div>Count: {count}</div>

Check it out:

https://svelte.dev/repl/70cff59aee444dd3ab772a244bd8fa36?version=3.48.0

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM