使用Zustand结合Immer中间件时,如何调用状态中自定义类的异步方法更新状态?
使用Zustand结合Immer中间件时,如何调用状态中自定义类的异步方法更新状态?
我理解你碰到的这个痛点——在Zustand+Immer的组合里,想调用自定义类实例的异步方法更新状态时,总是碰到各种代理相关的报错。我来给你拆解问题,再提供几个可行的解决方案:
问题根源先理清
首先要明确两个核心限制:
- Zustand的
set回调(也就是Immer的produce函数)必须是同步的,当异步操作执行时,Immer创建的代理对象已经被撤销,所以会出现“Cannot perform 'set' on a proxy that has been revoked”的错误。 - 通过
getState()直接拿到的状态实例是Immer的只读代理,在set回调之外直接修改它的属性会触发“Cannot assign to read only property”的报错。
解决方案1:重构类方法,返回新实例(最推荐)
把自定义类里的异步方法改成静态方法,让它直接返回新的类实例,而不是修改原实例。这样你可以在set之外完成异步请求,拿到新实例后再同步替换状态中的对象:
修改AccountData类:
import { immerable } from "immer"; export class AccountData { [immerable] = true; constructor(public account: string | null, public password: string | null) {} // 改成静态异步方法,返回新的AccountData实例 static async fromStorage(): Promise<AccountData> { const [account, password] = await asyncRequest(); return new AccountData(account, password); } }
然后更新Zustand store的方法:
export const useAccountStore = create(immerMiddleware((set) => ({ accountData: new AccountData(null, null), async updateAccountDataFromStorage() { // 先在set外部完成异步请求,拿到新实例 const newAccountData = await AccountData.fromStorage(); // 同步替换状态中的实例,Immer能正确处理 set(state => { state.accountData = newAccountData; }); }, async updatePassword() { let newpwd = await asyncRequestPassword(); set(state => { state.accountData.password = newpwd; }); } })));
解决方案2:抽离异步逻辑,同步更新属性
如果不想修改类的结构,可以把异步请求的逻辑从类里抽出来,先拿到数据,再在set回调里同步修改实例的属性:
export const useAccountStore = create(immerMiddleware((set) => ({ accountData: new AccountData(null, null), async updateAccountDataFromStorage() { // 先完成异步请求,拿到需要更新的数据 const [account, password] = await asyncRequest(); // 在set回调里同步修改实例属性,Immer会处理代理的可变更新 set(state => { state.accountData.account = account; state.accountData.password = password; }); }, // ...其他方法 })));
这种方式不需要修改AccountData类,只是把原来类里fromStorage的逻辑移到了store方法中,避开了在set回调里执行异步操作的问题。
解决方案3:分离异步获取与同步更新(保留类方法)
如果一定要保留类的实例方法,可以把异步获取数据和同步更新属性拆成两个方法:一个异步方法负责拿数据,一个同步方法负责更新自身属性,然后在store里分步骤执行:
修改AccountData类:
import { immerable } from "immer"; export class AccountData { [immerable] = true; constructor(public account: string | null, public password: string | null) {} // 异步获取数据,不修改自身,返回数据对象 async fetchFromStorage(): Promise<{account: string | null; password: string | null}> { const [account, password] = await asyncRequest(); return { account, password }; } // 同步更新自身属性,供外部在set回调中调用 update(data: {account: string | null; password: string | null}) { this.account = data.account; this.password = data.password; } }
然后更新store方法:
export const useAccountStore = create(immerMiddleware((set) => ({ accountData: new AccountData(null, null), async updateAccountDataFromStorage() { // 先拿到当前实例,调用异步方法获取数据(这里只是读操作,不会触发代理报错) const currentAccount = useAccountStore.getState().accountData; const updateData = await currentAccount.fetchFromStorage(); // 在set回调里调用同步的update方法修改属性 set(state => { state.accountData.update(updateData); }); }, // ...其他方法 })));
总结建议
尽量遵循Zustand+Immer的设计思路:异步操作放在set之外完成,拿到数据后再同步更新状态。这样既符合状态管理的最佳实践,也能避免Immer代理带来的各种问题。如果依赖自定义类的逻辑,优先考虑让类的方法只负责数据处理,把状态更新的逻辑交给Zustand的set回调来处理。
备注:内容来源于stack exchange,提问作者お休みなさい
相关产品推荐
相关产品推荐

