我正试图让我的苗条应用程序对数据存储中定义的字典的变化做出反应
在下面的代码REPL中,当创建新项时,调用createNewItem函数,并且在ante和post之间更新user存储,但是即使user显示,也不会调用getOptions函数。Options 明显改变了。有什么问题吗?
App.svelete
<select bind:value={selectedOption}>
{#each user_options as option}
<option value={option}>{option}</option>
{/each}
</select>
<button on:click={addNewItem}>New item</button>
<NewItem />
<script>
import {user, state, elementDisplay } from './data'
import NewItem from "./NewItem.svelte";
$: user_options = getOptions($user.options);
$: new_item = createNewItem($state.new_item);
let selectedOption = "";
function getOptions(user_options) {
console.log('user changed')
let options = []
if (state.new_item != '') {
user_options[$user.new_item] = ''
}
for (const [option, value] of Object.entries(user_options)) {
options.push(option)
}
return options
}
function createNewItem(new_item) {
if (new_item.length > 0 ) {
console.log('ante', $user)
$user.options[new_item] = '';
console.log('post', $user)
}
}
function addNewItem() {
elementDisplay('new-item', 'block')
document.getElementById('new-item-value').focus();
}
</script>
NewItem.svelte
<div id="new-item">
<input type="text" id="new-item-value">
<div class="buttons">
<button class="select-button" on:click={useItem}>Use</button>
<button class="select-button" on:click={close}>Close</button>
</div>
</div>
<style>
#new-item {
display: none;
}
</style>
<script>
import { state, elementDisplay } from './data'
function useItem() {
let input_field = document.getElementById('new-item-value')
if (input_field.value) {
$state.new_item = input_field.value
}
close()
}
function close() {
elementDisplay('new-item', 'none');
}
</script>
data.js
import { writable } from 'svelte/store';
export let user = writable({
new_item: '',
options: {
"2": "Option 2",
"1": "Option 1",
"3": "Option 3",
}
}
);
export let state = writable({
new_item: '',
});
export function elementDisplay(item_name, display) {
let item = document.getElementById(item_name);
item.style.display = display;
};
TL;DR:交换这两个$:
反应语句的顺序,您将得到期望的结果。
通常$-语句的顺序无关紧要。然而,在您的情况下,createNewItem
函数和$user
变量之间存在隐藏的因果关系,而svelte编译器没有注意到。
发生了什么事?这一切都归结为如何快速编译响应式语句。
$: user_options = getOptions($user.options);
$: new_item = createNewItem($state.new_item);
/** ABOVE IS COMPILED INTO BELOW */
$$self.$$.update = () => {
if ($$self.$$.dirty & /*$user*/ 8) {
$$invalidate(0, user_options = getOptions($user.options));
}
if ($$self.$$.dirty & /*$state*/ 16) {
$: new_item = createNewItem($state.new_item);
}
};
注意,所有反应性语句都被打包到一个$$.update
函数中。
你的连锁反应是这样的:
1) NewItem.svelte#useItem() -> $state.new_item = "..." ->
2) $: new_item = createNewItem($state.new_item) -> $user.options[new_item] = '' ->
3) $: user_options = getOptions($user.options);
隐藏的因果关系(或"依赖性");)位于createNewItem($state.new_item)
(第二个$-语句)中,其中$user.options
发生了突变。然后,它又导致getOptions($user.options)
(第一个$-语句)运行。
这就是你的意图。然而,苗条的解释方式是这样的:
- 编译器将两个$-语句打包到同一个
$$.update
函数中,该函数将在每个更新周期(源)中只执行一次。 useItem() -> $state.new_item
触发一个新的更新周期,所以$$.update()
被调用来运行那些$:
的反应。if ($$self.$$.dirty & /*$user*/ 8)
检查首先运行,它看到$user
在这一点上没有脏,所以它继续前进。if ($$self.$$.dirty & /*$state*/ 16)
看到$state
是脏的,所以运行createNewItem($state.new_item)
如果我们再次运行
if ($$self.$$.dirty & /*$user*/ 8)
检查,我们现在会看到$user
脏了!但是已经太晚了,因为按照苗条编译的方式,我们已经越过了那个点。$: new_item = createNewItem($state.new_item);
$: user_options = getOptions($user.options);
实际上,文档中有一个非常类似的例子,讨论了这个警告:
需要注意的是,响应性块是在编译时通过简单的静态分析排序的,编译器查看的是在块本身中分配和使用的变量,而不是在它们调用的任何函数中。这意味着在以下示例中,当x被更新时,yDependent将不会被更新:[…]
将$:yDependent = y这行移到$:setY(x)下面将导致当x更新时yDependent被更新。
然而,我个人会完全避免这种深度(和隐藏)连锁反应。对我来说,这是一个非常危险的反模式。依赖性一开始是隐藏的,反应链是复杂的,加上这个很难注意到的顺序警告,为什么要让自己陷入这种混乱呢?
最好将你的代码组织成更清晰的结构,而不是直接将糟糕的设计连接起来。