You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:39:34