Svelte Store的set函数在绑定对象时的工作机制及值对比问题
自定义Svelte Store对象类型值的新旧值对比问题
问题描述
我需要创建带有自定义逻辑的Svelte Store,参考相关代码实现了一个包含subscribe和set函数的简易Store。但当初始化Store为对象并对其某个键创建双向绑定时,调用set函数时发现当前value已被设置为刚传入的newValue,而使用原始值类型初始化Store时不会出现此问题。我想知道如何在设置新值前对比Store的新旧值,相关代码如下:
<script> const writable = (initialValue) => { let value = initialValue; // store的内容 let subs = []; // 订阅者处理函数 const subscribe = (handler) => { handler(value); // 调用处理函数传入当前值 subs = [...subs, handler]; // 将处理函数加入订阅者数组 return () => subs = subs.filter((sub) => sub !== handler); // 返回取消订阅函数 }; const set = (newValue) => { // 这段对比逻辑对对象不起作用 console.log(`value: ${JSON.stringify(value)}`); console.log(`new value: ${JSON.stringify(newValue)}`); console.log(newValue == value); // 对比新旧值 value = newValue; // 更新值 subs.forEach((sub) => sub(value)); // 更新订阅者 }; return {subscribe, set}; // store契约 }; let numberStore = writable("5"); let objectStore = writable({foo: "bar"}); </script> <p>{$numberStore}</p> <input bind:value={$numberStore}> <p>{JSON.stringify($objectStore)}</p> <input bind:value={$objectStore.foo}>
问题原因
对象是引用类型,当你使用bind:value={$objectStore.foo}时,Svelte会直接修改Store内部对象的foo属性——因为$objectStore指向的是Store内部的对象引用,外部修改会直接同步到Store的value变量,导致调用set前,旧值已经被改成了新值。而原始值是值类型,绑定操作会生成新值,不会修改Store内部的原始引用,所以对比逻辑正常。
解决方案
方案1:强制通过set方法更新对象(推荐)
避免直接修改Store内部的对象属性,改用set方法传入新对象,手动处理绑定逻辑:
修改模板部分的输入框:
<input value={$objectStore.foo} on:input={(e) => objectStore.set({...$objectStore, foo: e.target.value})} >
这种方式下,每次输入都会创建新的对象并传入set,此时Store内部的value还是旧对象,能正常对比新旧值。
方案2:深拷贝+深对比(保留双向绑定)
如果想保留bind:value={$objectStore.foo}的写法,需要在Store内部对对象做深拷贝,隔离外部引用,同时用深对比判断值是否变化:
修改后的writable函数:
const writable = (initialValue) => { // 深拷贝初始值,避免外部直接修改引用 let value = JSON.parse(JSON.stringify(initialValue)); let subs = []; const subscribe = (handler) => { handler(value); subs = [...subs, handler]; return () => subs = subs.filter(sub => sub !== handler); }; // 深对比两个值是否相等 const isEqual = (a, b) => { return JSON.stringify(a) === JSON.stringify(b); }; const set = (newValue) => { // 深拷贝新值,避免后续外部修改影响内部状态 const copiedNewValue = JSON.parse(JSON.stringify(newValue)); if (isEqual(value, copiedNewValue)) return; // 值未变化则跳过更新 console.log(`旧值: ${JSON.stringify(value)}`); console.log(`新值: ${JSON.stringify(copiedNewValue)}`); value = copiedNewValue; subs.forEach(sub => sub(value)); }; return { subscribe, set }; };
注意事项
JSON.stringify的深拷贝/深对比有局限性,无法处理函数、Symbol、循环引用等特殊类型。如果你的Store需要处理复杂对象,可以使用第三方库(如lodash的_.cloneDeep和_.isEqual)替代。- 深拷贝会带来一定性能开销,若Store频繁更新且对象体积大,建议优先使用方案1。
内容的提问来源于stack exchange,提问作者treesandgreens
相关产品推荐
相关产品推荐

