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

使用Zustand结合Immer中间件时,如何调用状态中自定义类的异步方法更新状态?

使用Zustand结合Immer中间件时,如何调用状态中自定义类的异步方法更新状态?

我理解你碰到的这个痛点——在Zustand+Immer的组合里,想调用自定义类实例的异步方法更新状态时,总是碰到各种代理相关的报错。我来给你拆解问题,再提供几个可行的解决方案:

问题根源先理清

首先要明确两个核心限制:

  1. Zustand的set回调(也就是Immer的produce函数)必须是同步的,当异步操作执行时,Immer创建的代理对象已经被撤销,所以会出现“Cannot perform 'set' on a proxy that has been revoked”的错误。
  2. 通过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,提问作者お休みなさい

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 14:58:03