Svelte store assignment 调用默认 writable().set 然后自定义 .set?

Svelte store assignment calls default writable().set and then custom .set?

Relevant REPL

我有一个带有自定义 set 方法的简单可写存储:

import { writable } from 'svelte/store';
function createState() {
    const {subscribe, set, update} = writable({a: 0, b: 0});
    return {
        subscribe,
        set: (newState) => {
            console.log(newState);
            // set(newState); // I would expect `state` to be unchanged without this
        }
    };
};

export const state = createState();

当我调用 state.set(<some new value>) 时,新值被记录到控制台,而 state 的值实际上并没有改变。这就是我所期望的。
但是,如果我分配 $state = <some new value>state 的值发生变化,并且 then set 将其记录到控制台。为什么(以及如何)发生这种情况,有没有办法在不重新实现的情况下绕过它 writable?

谢谢!

在您的 REPL 的 JS 输出中,您可以看到以下内容:

function instance($$self, $$props, $$invalidate) {
    let $state;
    component_subscribe($$self, state, $$value => $$invalidate(0, $state = $$value));
    set_store_value(state, $state.a = 1, $state); // changes the value of state?!
    set_store_value(state, $state = { d: 0 }); // ''
    state.set({ c: 0 }); // no effect, as expected
    return [$state];
}

使用 shorthand 反应式赋值 $<store> 编译成 set_store_value() 调用,这是定义的一个精巧的内部方法 thusly:

export function set_store_value(store, ret, value = ret) {
    store.set(value);
    return ret;
}

因此,正如您所期望的那样,分配的值实际上传递给您商店的 set 函数。

但是,在上面的JS输出中可以看到,最终返回的值是一个local变量,叫做$state$ 符号 不是 反应修饰符,只是名称的一部分)。在那些 set_store_value() 调用期间,您可以看到这个局部变量被赋予了与传递给您商店的 set 方法相同的值(实际上,局部变量被赋予了那个值,然后它本身传递给 set_store_value() 方法):

set_store_value(state, $state.a = 1, $state); // changes the value of state?!
set_store_value(state, $state = { d: 0 }); // ''

我希望这种行为是某种乐观的 look-ahead/resolution。

也许 Svelte store contract 中暗示传递给商店的 set 方法的值 必须 实际上相应地修改商店,在这种情况下乐观的解决方法总能产生连贯的结果?

希望 Rich Harris(或其他 Svelte 贡献者)能看到您的问题并提供更明确的答案。

扩展 Thomas Hennes 的精彩分析:

set_store_value(state, $state.a = 1, $state);

此内部调用更新内部存储模型 ($state.a = 1),而不使用 setupdate 方法。对我来说,这有点出乎意料,可能是一个小错误。至少,这是我们在设计复杂的自定义商店并希望使用商店简写时需要牢记的事情。

这是预期的行为!暂时忘记商店,并且 - 因为 state.set 在你的例子中是一个 noop - 删除那行:

<script>
    // import { state } from "./stores.js";

    let pojo = { a: 0, b: 0 };
    pojo.a = 1;
    pojo = {d: 0};

    //state.set({c: 0}); // no effect, as expected
</script>

<h1>state: {JSON.stringify(pojo)}</h1> 

结果...

state: {"d":0}

...正是您所期望的。

当我们用 $state 替换 pojo 时,我们仍然在分配(或改变)一个具有相同反应特性的局部变量。唯一的区别是我们 调用 state.set。通常这会导致商店价值发生变化,并反映给订阅者,但由于您的自定义商店没有这样做,我们只剩下上次 $state 被分配触及后的价值运算符。