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
相关产品推荐
相关产品推荐

