首页 > 解决方案 > 当函数中的变量更改时,不会触发 Svelte 反应性

问题描述

我在这里有点困惑,不幸的是我无法在 svelte 的不和谐频道上找到任何解决方案,所以我开始了......

我有两个类的一个相当基本的例子,让它们成为AppCompApp创建一个Comp实例,然后value在单击按钮后更新此实例。

Comp 实例应将此值设置为不同的变量 ( inputValue),并在更改该变量时触发validate(inputValue)它是反应性的。这是一个 REPL:https ://svelte.dev/repl/1df2eb0e67b240e9b1449e52fb26eb14?version=3.25.1

App.svelte:

<script>
    import Comp from './Comp.svelte';
    
    let value = 'now: ' + Date.now();
    
    function clickHandler(e) {
        value = 'now ' + Date.now();
    }
</script>

<Comp
    bind:value={value}
/>
<button type="button" on:click={clickHandler}>change value</button>

比较苗条:

<script>
    import { onMount } from 'svelte';

    export let value;

    let rendered = false;
    let inputValue = '';

    $: validate(inputValue); // This doesn't execute. Why?

    function validate(val) {
        console.log('validation:', val); 
    }

    onMount(() => {
        rendered = true;
    });

    $: if (rendered) {
        updateInputValue(value);
    }

    function updateInputValue(val) {
        console.log('updateInputValue called!');
        if (!value) {
            inputValue = '';
        }
        else {
            inputValue = value;
        }
    }
</script>

<input type="text" bind:value={inputValue}>

因此,一旦更改了值:

  1. 反应if (rendered) {...}条件称为
  2. updateInputValue被调用并被inputValue改变。HTML 输入元素更新为此值。
  3. validate(inputValue)从未对这种变化做出反应 -为什么?

updateInputValue如果我在反应条件中省略了对函数的额外调用if (rendered)并将updateInputValue函数体的代码直接放入条件中,则validate(inputValue)正确触发,即:

// works like this  
$: if (rendered) {
    if (!value) {
        inputValue = '';
    }
    else {
        inputValue = value;
    }
}

那么为什么在函数中更新它不起作用呢?

标签: javascriptsveltesvelte-3

解决方案


@johannchopin 的回答揭示了这个问题。


您可以阅读我的博客文章,以稍微深入地解释反应式声明的工作原理,这里是 tl;dr:

  • 反应式声明是批量执行的

    svelte 批处理所有更改以在下一个更新周期中更新它们,并且在更新 DOM 之前,它将执行响应式声明以更新响应式变量。

  • 反应式声明按照它们的依赖顺序执行。

    反应式声明是批量执行的,每个声明执行一次。一些声明相互依赖,例如:

    let count = 0;
    $: double = count * 2;
    $: quadruple = double * 2;
    

    在这种情况下,quadruple取决于double。因此,无论您的反应性声明的顺序如何,$: double = count * 2;之前$: quadruple = double * 2或相反,前者应该并且在后者之前执行。

    Svelte将按照依赖顺序对声明进行排序。

    在彼此没有依赖关系的情况下:

    $: validate(inputValue);
    $: if (rendered) updateInputValue(value);
    

    第一个语句取决于validateand inputValue,第二个语句取决于rendered, updateInputValueand value,声明保持原样。


现在,了解了反应式声明的这两种行为,让我们来看看你的REPL

当您更改或时inputValue,Svelte 将批量更改,并开始新的更新周期。renderedvalue

在更新 DOM 之前,Svelte 将一次性执行所有的反应式声明。

validate(inputValue);因为and语句之间没有依赖关系if (rendered) updateInputValue(value);,(如前所述),它们将按顺序执行。

如果您更改renderedvalue仅更改,validate(inputValue)则不会执行第一个语句(),同样,如果您更改inputValue第二个语句(if (rendered) updateInputValue(value)),则不会执行。

现在,在updateInputValue您更改 的值inputValue,但因为我们已经处于更新周期,我们不会开始一个新的。

这通常不是问题,因为如果我们按照依赖顺序对响应式声明进行排序,更新所依赖变量的语句将在依赖变量的语句之前执行。


因此,知道出了什么问题,您可以寻求一些“解决方案”。

  1. 手动重新排序反应式声明语句,尤其是在执行顺序存在隐式依赖时。

请参阅此REPL中订购反应式声明的区别

因此,将您的 REPL 更改为:

$: if (rendered) {
  updateInputValue(value);
}
$: validate(inputValue);

见 REPL

  1. 在响应式声明中显式定义依赖关系
$: validate(inputValue);

$: if (rendered) {
    inputValue = updateInputValue(value);
}
    
function updateInputValue(val) {
    console.log('updateInputValue called!');
    if (!value) {
        return '';
    }
    else {
        return value;
    }
}

见 REPL


推荐阅读