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

Svelte自定义Store如何禁止直接修改状态,仅允许通过addItem()操作?

问题原因

你遇到的问题核心是引用类型的状态泄漏:当通过$store访问自定义Store的状态时,拿到的是Store内部存储对象的直接引用。虽然你没暴露set/update方法,但直接修改$store.items会改变Store内部的状态(因为对象是引用传递)——只是这次修改没触发订阅更新,所以UI没立刻变化;但下次调用addItem时,update会基于已经被篡改的内部状态生成新对象,UI就会显示之前偷偷修改的内容。

解决方法

下面提供几种可行的方案,按需选择:

方案1:包装subscribe方法,返回状态副本

修改自定义Store的subscribe方法,每次向组件传递状态时返回一个独立副本,这样组件拿到的是和Store内部状态无关的新对象,修改副本不会影响原状态。

<script>
    import { writable } from 'svelte/store';
    
    function itemStore() {
        const { subscribe: originalSubscribe, update } = writable({ items: [], name: 'test' });

        // 包装原subscribe,返回状态的浅拷贝副本
        const subscribe = (run) => {
            return originalSubscribe((state) => {
                // 若有嵌套对象,需要用深拷贝(比如JSON.parse(JSON.stringify(state)))
                const stateCopy = { ...state, items: [...state.items] };
                run(stateCopy);
            });
        };

        return {
            subscribe,
            addItem: () => update(n => ({ ...n, items: [...n.items, new Date().valueOf()] })),
        };
    }
    
    const store = itemStore()
</script>

<h1>The count is {$store.items.length}</h1>

{#each $store.items as i}
    <div>{i}</div>
{/each}

<button on:click={store.addItem}>Add item</button>
<br>
<button on:click={() => { $store.items = [...$store.items, new Date().valueOf()+' - added directly'] }}>Add item to object directly</button>

方案2:冻结状态对象,从根源阻止修改

使用Object.freeze冻结每次更新后的状态对象,让外部无法直接修改属性(严格模式下会直接报错)。注意这是浅冻结,若有嵌套复杂对象需要递归冻结。

<script>
    import { writable } from 'svelte/store';
    
    function itemStore() {
        // 初始状态先冻结
        const initialState = Object.freeze({ items: [], name: 'test' });
        const { subscribe, update } = writable(initialState);

        return {
            subscribe,
            addItem: () => update(n => {
                // 每次更新都创建新的冻结对象
                const newItems = Object.freeze([...n.items, new Date().valueOf()]);
                return Object.freeze({ ...n, items: newItems });
            }),
        };
    }
    
    const store = itemStore()
</script>

方案3:用Derived Store做状态隔离

如果不想修改原自定义Store,可以创建一个Derived Store,返回原Store状态的副本,组件使用这个Derived Store即可,修改副本不会影响原Store的状态。

<script>
    import { writable, derived } from 'svelte/store';
    
    function itemStore() {
        const { subscribe, update } = writable({ items: [], name: 'test' });

        return {
            subscribe,
            addItem: () => update(n => ({ ...n, items: [...n.items, new Date().valueOf()] })),
        };
    }
    
    const store = itemStore();
    // 创建只读的Derived Store,返回状态副本
    const readOnlyStore = derived(store, $state => ({ ...$state, items: [...$state.items] }));
</script>

<h1>The count is {$readOnlyStore.items.length}</h1>

{#each $readOnlyStore.items as i}
    <div>{i}</div>
{/each}

<button on:click={store.addItem}>Add item</button>
<br>
<button on:click={() => { $readOnlyStore.items = [...$readOnlyStore.items, new Date().valueOf()+' - added directly'] }}>Add item to object directly</button>

内容的提问来源于stack exchange,提问作者user1570901

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 11:10:36