当函数中的变量发生变化时,不会触发 Svelte 反应性

Fyg*_*ygo 4 javascript svelte svelte-3

我在这里有点困惑,不幸的是我无法在 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>
Run Code Online (Sandbox Code Playgroud)

Comp.svelte:

<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}>
Run Code Online (Sandbox Code Playgroud)

因此,一旦值发生变化:

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

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

// works like this  
$: if (rendered) {
    if (!value) {
        inputValue = '';
    }
    else {
        inputValue = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Tan*_*Hau 8

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


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

  • 反应式声明是批量执行

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

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

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

    let count = 0;
    $: double = count * 2;
    $: quadruple = double * 2;
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    在相互之间没有依赖关系的情况下:

    $: validate(inputValue);
    $: if (rendered) updateInputValue(value);
    
    Run Code Online (Sandbox Code Playgroud)

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


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

当您更改inputValuerendered或者value,将斯维尔特批次的变化,并开始一个新的更新周期。

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

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

如果更改renderedor valueonly,则第 1 条语句 ( validate(inputValue)) 不会执行,同样,如果更改inputValueif (rendered) updateInputValue(value)则不会执行第 2 条语句 ( )。

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

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


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

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

请参阅此REPL 中对反应性声明进行排序的区别

因此,将您的 REPL 更改为:

$: if (rendered) {
  updateInputValue(value);
}
$: validate(inputValue);
Run Code Online (Sandbox Code Playgroud)

见 REPL

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

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

见 REPL

  • 很好的答案;)再次感谢您。 (2认同)

joh*_*pin 6

真的很奇怪(我无法真正解释它),但是如果您将反应式语句放在$: validate(inputValue);函数updateInputValue声明之后,它就会按预期工作:

<script>
    import { onMount } from 'svelte';
    
    export let value;
    
    let rendered = false;
    let inputValue = '';

    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;
        }
    }
    
    $: validate(inputValue);
</script>
Run Code Online (Sandbox Code Playgroud)

检查这个REPL