当函数中的变量更改时不会触发 Svelte 反应
Svelte reactivity not triggering when variable changed in a function
我在这里有点困惑,不幸的是我在 svelte 的 discord 频道上找不到任何解决方案,所以我开始了...
我有两个 类 的相当基本的例子,假设它们是 App
和 Comp
。
App
创建一个 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>
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}>
所以一旦值改变:
- 被动
if (rendered) {...}
条件被调用
updateInputValue
被调用,inputValue
被改变。 HTML 输入元素更新为此值。
validate(inputValue)
从未对此更改做出反应 - 为什么?
如果我在反应式 if (rendered)
条件中省略对 updateInputValue
函数的额外调用并将 updateInputValue
函数体的代码直接放在条件中,那么 validate(inputValue)
是正确触发,即:
// works like this
$: if (rendered) {
if (!value) {
inputValue = '';
}
else {
inputValue = value;
}
}
那么为什么在函数中更新时不起作用?
真的很奇怪(我真的无法解释)但是如果你把响应语句 $: 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>
检查此 REPL。
这里是原始 post 问题的最小示例和几个解决方法:
<script>
let nb
let n=0
$: console.log("nb1:",nb)
$: update(n)
$: console.log("nb2:",nb)
function update(v) {
console.log("update",v)
nb = v
}
</script>
<h1 on:click={()=>n=Math.floor(Math.random()*100)}>click: {nb}</h1>
<h1 on:click={()=>update(Math.floor(Math.random()*100))}>click: {nb}</h1>
nb
的第一个日志不会在 n
被分配一个新值并且 $: update(n)
运行s 由于分配而被触发。 nb
的第二条日志是运行,因为它在赋值给nb
.
之后
如果直接调用 update
-函数(最后 h1
)一切正常。
如果您在顶层为 nb
赋值,则一切正常:
<script>
let nb
let n=0
$: console.log("nb1:",nb)
$: nb = update(n)
$: console.log("nb2:",nb)
function update(v) {
console.log("update",v)
return v
}
</script>
<h1 on:click={()=>n=Math.floor(Math.random()*100)}>click: {nb}</h1>
<h1 on:click={()=>update(Math.floor(Math.random()*100))}>click: {nb}</h1>
我认为这是一个错误。
@johannchopin 的 揭示了这个问题。
您可以阅读 my blogpost for slightly in-depth explanation of how reactive declaration works,这里是 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);
第一个语句依赖于validate
和inputValue
,第二个语句依赖于rendered
、updateInputValue
和value
,声明在顺序中保留原样。
现在,了解了响应式声明的这 2 个行为,让我们来看看您的 REPL。
当您更改 inputValue
、rendered
或 value
时,Svelte 将对更改进行批处理,并开始新的更新周期。
就在更新 DOM 之前,Svelte 将一次性执行所有响应式声明。
因为 validate(inputValue);
和 if (rendered) updateInputValue(value);
语句之间没有依赖关系,(如前所述),它们将按顺序执行。
如果你只改变rendered
或value
,第一条语句(validate(inputValue)
)将不会被执行,同样,如果你改变inputValue
第二条语句(if (rendered) updateInputValue(value)
) 不会被执行。
现在,在 updateInputValue
中您更改了 inputValue
的值,但是因为我们已经处于更新周期中,所以我们不会开始新的周期。
这通常不是问题,因为如果我们按依赖顺序对响应式声明进行排序,更新依赖变量的语句将在依赖变量的语句之前执行。
所以,知道出了什么问题,您可以寻求一些“解决方案”。
- 手动重新排序反应式声明语句,尤其是当执行顺序存在隐式依赖时。
在此 REPL
中查看对响应式声明进行排序的区别
因此将您的 REPL 更改为:
$: if (rendered) {
updateInputValue(value);
}
$: validate(inputValue);
- 在响应式声明中明确定义依赖关系
$: validate(inputValue);
$: if (rendered) {
inputValue = updateInputValue(value);
}
function updateInputValue(val) {
console.log('updateInputValue called!');
if (!value) {
return '';
}
else {
return value;
}
}
我在这里有点困惑,不幸的是我在 svelte 的 discord 频道上找不到任何解决方案,所以我开始了...
我有两个 类 的相当基本的例子,假设它们是 App
和 Comp
。
App
创建一个 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>
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}>
所以一旦值改变:
- 被动
if (rendered) {...}
条件被调用 updateInputValue
被调用,inputValue
被改变。 HTML 输入元素更新为此值。validate(inputValue)
从未对此更改做出反应 - 为什么?
如果我在反应式 if (rendered)
条件中省略对 updateInputValue
函数的额外调用并将 updateInputValue
函数体的代码直接放在条件中,那么 validate(inputValue)
是正确触发,即:
// works like this
$: if (rendered) {
if (!value) {
inputValue = '';
}
else {
inputValue = value;
}
}
那么为什么在函数中更新时不起作用?
真的很奇怪(我真的无法解释)但是如果你把响应语句 $: 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>
检查此 REPL。
这里是原始 post 问题的最小示例和几个解决方法:
<script>
let nb
let n=0
$: console.log("nb1:",nb)
$: update(n)
$: console.log("nb2:",nb)
function update(v) {
console.log("update",v)
nb = v
}
</script>
<h1 on:click={()=>n=Math.floor(Math.random()*100)}>click: {nb}</h1>
<h1 on:click={()=>update(Math.floor(Math.random()*100))}>click: {nb}</h1>
nb
的第一个日志不会在 n
被分配一个新值并且 $: update(n)
运行s 由于分配而被触发。 nb
的第二条日志是运行,因为它在赋值给nb
.
之后
如果直接调用 update
-函数(最后 h1
)一切正常。
如果您在顶层为 nb
赋值,则一切正常:
<script>
let nb
let n=0
$: console.log("nb1:",nb)
$: nb = update(n)
$: console.log("nb2:",nb)
function update(v) {
console.log("update",v)
return v
}
</script>
<h1 on:click={()=>n=Math.floor(Math.random()*100)}>click: {nb}</h1>
<h1 on:click={()=>update(Math.floor(Math.random()*100))}>click: {nb}</h1>
我认为这是一个错误。
@johannchopin 的
您可以阅读 my blogpost for slightly in-depth explanation of how reactive declaration works,这里是 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);
第一个语句依赖于
validate
和inputValue
,第二个语句依赖于rendered
、updateInputValue
和value
,声明在顺序中保留原样。
现在,了解了响应式声明的这 2 个行为,让我们来看看您的 REPL。
当您更改 inputValue
、rendered
或 value
时,Svelte 将对更改进行批处理,并开始新的更新周期。
就在更新 DOM 之前,Svelte 将一次性执行所有响应式声明。
因为 validate(inputValue);
和 if (rendered) updateInputValue(value);
语句之间没有依赖关系,(如前所述),它们将按顺序执行。
如果你只改变rendered
或value
,第一条语句(validate(inputValue)
)将不会被执行,同样,如果你改变inputValue
第二条语句(if (rendered) updateInputValue(value)
) 不会被执行。
现在,在 updateInputValue
中您更改了 inputValue
的值,但是因为我们已经处于更新周期中,所以我们不会开始新的周期。
这通常不是问题,因为如果我们按依赖顺序对响应式声明进行排序,更新依赖变量的语句将在依赖变量的语句之前执行。
所以,知道出了什么问题,您可以寻求一些“解决方案”。
- 手动重新排序反应式声明语句,尤其是当执行顺序存在隐式依赖时。
在此 REPL
中查看对响应式声明进行排序的区别因此将您的 REPL 更改为:
$: if (rendered) {
updateInputValue(value);
}
$: validate(inputValue);
- 在响应式声明中明确定义依赖关系
$: validate(inputValue);
$: if (rendered) {
inputValue = updateInputValue(value);
}
function updateInputValue(val) {
console.log('updateInputValue called!');
if (!value) {
return '';
}
else {
return value;
}
}