如何让 Svelte 对变量的变化做出反应

Psi*_*man 2 svelte

我正在尝试让我的 Svelte 应用程序对数据存储中定义的字典的更改做出反应

在以下代码REPL中,当创建新项目时会调用createNewItem函数,并且用户存储会在该函数中的antepost显示之间更新,但即使user.options明显更改,也不会调用getOptions函数。问题是什么?

应用程序.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>
Run Code Online (Sandbox Code Playgroud)

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

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

hac*_*ape 7

TL;DR:交换这两个反应语句的顺序$:,您将得到所需的结果。

解释

通常 $ 语句的顺序并不重要。createNewItem但是,在您的情况下,函数和变量之间存在隐藏的因果关系$user,而 svelte 编译器无法注意到这一点。

发生了什么?这一切都归结为 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);
    }
};
Run Code Online (Sandbox Code Playgroud)

请注意,所有反应式语句都打包到一个$$.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);
Run Code Online (Sandbox Code Playgroud)

隐藏的因果关系(或者“依赖性”,如果你喜欢的话)位于createNewItem($state.new_item)(第二个 $ 语句)内部,其中$user.options发生了变异。然后它依次导致getOptions($user.options)(第一个 $ 语句)运行。

这就是你的意图。然而 svelte 的解释方式是这样的:

  • 编译器将两个 $ 语句打包到同一个$$.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但为时已晚,因为按照 svelte 编译的方式,我们已经超越了这一点。

解决方案

快速修复,您只需交换 $ 语句的顺序:

$: new_item = createNewItem($state.new_item);
$: user_options = getOptions($user.options);
Run Code Online (Sandbox Code Playgroud)

事实上,svelte 文档有一个非常相似的例子来讨论这个警告:

值得注意的是,反应式块是在编译时通过简单的静态分析进行排序的,并且编译器查看的所有变量都是分配给块本身并在块本身内使用的变量,而不是在它们调用的任何函数中。这意味着在以下示例中更新 x 时,yDependent 将不会更新:[...]

将行 $: yDependent = y 移动到 $: setY(x) 下面将导致 yDependent 在 x 更新时更新。

然而,我个人会完全避免这种深度(和隐藏的)连锁反应。对我来说,这是一个巨大的危险反模式。依赖性从一开始就是隐藏的,反应链错综复杂,再加上这个很难注意到的顺序警告,为什么你会让自己陷入这种混乱之中呢?

更好地将代码组织成更清晰的结构,而不是跳线不良设计。