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

自定义Svelte Store结合Immer重复刷新及数据绑定失效问题

Svelte Immer自定义Store问题修复

问题1:未变更数据仍触发订阅刷新

原因

  • 原producer方法直接调用store.update,即便Immer返回原对象(无修改操作时),store.update仍会触发订阅回调
  • 自定义set方法依赖闭包保存的初始value,而非store的实时最新状态,导致新旧值对比逻辑失效

修复方案

在immerWritable内部维护当前状态的实时引用,所有更新操作先通过Immer生成新值,仅当新值与当前状态不同时才触发store更新,避免无意义的订阅刷新。

问题2:数据绑定失效

原因

  • 开启immutable={true}后,Svelte不会检测对象内部属性的变化,直接绑定$store.sub.a无法触发store状态更新
  • 原update方法依赖闭包的初始value,无法获取store最新状态,导致更新逻辑错误

修复方案

使用本地变量中转绑定值,通过事件触发store的immutable更新(推荐,符合immutable设计原则);也可关闭immutable选项(不推荐,会增加Svelte的深度检测开销)。


修改后的自定义Store代码

import { writable } from "svelte/store";
import produce from "immer";

const immerWritable = (initialValue) => {
  const store = writable(initialValue);
  
  // 实时同步当前状态,避免依赖闭包初始值
  let currentValue = initialValue;
  const internalUnsubscribe = store.subscribe(value => {
    currentValue = value;
  });

  function set(newValue) {
    // 仅当值真正变化时更新store
    if (newValue !== currentValue) {
      store.set(currentValue = newValue);
    }
  }

  const producer = (fn) => {
    const newValue = produce(currentValue, fn);
    set(newValue);
  };

  const update = (fn) => {
    const newValue = produce(currentValue, fn);
    set(newValue);
  };

  // 清理内部订阅,避免内存泄漏
  function destroy() {
    internalUnsubscribe();
  }

  return {
    producer,
    set,
    update,
    subscribe: store.subscribe,
    destroy
  };
};

const store = immerWritable({
  sub: { a: 1, b: 2 },
  sub2: { a: 1, b: 2 }
});

const actions = {
  setSubA_10: () =>
    store.producer((draft) => {
      draft.sub.a = 10;
    })
};

export default { ...store, ...actions };

修改后的App.svelte代码

<script>
  import store from "./store";
  import { onDestroy } from "svelte";
  let changed = 0;
  // 本地变量中转绑定值,遵循immutable原则
  let subA = $store.sub.a;

  const unsubscribe = store.subscribe(s => {
    changed++;
    // 同步本地变量与store状态
    subA = s.sub.a;
  });

  // 输入变化时通过Immer更新store
  const updateSubA = () => {
    store.producer(draft => {
      draft.sub.a = Number(subA);
    });
  };

  onDestroy(() => {
    unsubscribe();
    store.destroy();
  });
</script>

<div>store changed {changed}</div>
<button on:click={store.setSubA_10}>set sub.a to 10</button>
<input bind:value={subA} type="number" on:input={updateSubA}/>

内容的提问来源于stack exchange,提问作者Sami Al-Subhi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:01:36