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

如何在React-Hook-Form表单中用Immer更新Zustand包裹列表状态?

问题解决方案

核心问题分析

你遇到的draft is not a function错误,本质是两个关键问题导致的:

  1. 状态结构设计错误:原store中parcel定义为单个SingleParcel对象,但业务需求是存储包裹列表,类型不匹配导致无法执行数组push操作。
  2. Immer使用方式错误:原代码中produce回调参数命名混乱,且错误操作了不存在的draft.parcel属性,触发变量未定义类错误。

修正后的完整代码

1. 调整状态结构与store实现

import create from "zustand";
import { produce } from "immer";
import { SingleParcel } from "../type/User";

// 初始化空包裹对象,确保必填字段有默认值
const initialParcel: SingleParcel = {
  parcelID: undefined,
  parcelWeight_kg: undefined,
  parcelHeight_cm: undefined,
  parcelWidth_cm: undefined,
  parcelLength_cm: undefined,
  parcelVolume_m3: undefined,
  parcelAmount: undefined,
  parcelAvailableTime: undefined,
  parcelDeliveryTime: undefined,
  parcelStartStreet: "",
  parcelStartStreetNumber: undefined,
  parcelStartAreaCode: undefined,
  parcelStartCity: undefined,
  parcelDestinationStreet: undefined,
  parcelDestinationStreetNumber: undefined,
  parcelDestinationAreaCode: undefined,
  parcelDestinationCity: undefined,
};

type ParcelQuoteState = {
  currentParcel: SingleParcel; // 当前正在编辑的单个包裹
  parcels: Array<{ id: number; data: SingleParcel }>; // 已保存的包裹列表
  setCurrentParcel: (input: Partial<SingleParcel>) => void; // 更新当前编辑的包裹
  addParcelToList: () => void; // 将当前包裹加入列表
  removeParcelFromList: (id: number) => void; // 删除列表中的指定包裹
  resetCurrentParcel: () => void; // 重置当前编辑的包裹
};

export const useParcelStore = create<ParcelQuoteState>((set) => ({
  currentParcel: initialParcel,
  parcels: [],
  setCurrentParcel: (input) => {
    set(produce((draft) => {
      Object.assign(draft.currentParcel, input);
    }));
  },
  addParcelToList: () => {
    set(produce((draft) => {
      // 生成唯一ID(实际项目建议用UUID或自增序列)
      const newId = Math.floor(Math.random() * 10000);
      // 深拷贝当前包裹存入列表,避免后续修改影响历史数据
      draft.parcels.push({ id: newId, data: { ...draft.currentParcel } });
      // 重置当前包裹,方便输入新的包裹信息
      draft.currentParcel = { ...initialParcel };
    }));
  },
  removeParcelFromList: (id) => {
    set(produce((draft) => {
      draft.parcels = draft.parcels.filter(item => item.id !== id);
    }));
  },
  resetCurrentParcel: () => {
    set({ currentParcel: { ...initialParcel } });
  },
}));

关键修改说明

  1. 状态拆分:将原单一的parcel拆分为currentParcel(存储当前编辑的单个包裹)和parcels(存储已保存的包裹列表),完全匹配"输入单个包裹→加入列表→继续添加"的业务流程。
  2. Immer正确用法:produce的回调参数是整个状态的草稿对象draft,直接操作draft上的属性即可完成不可变更新,避免了原代码中的变量名错误和类型不匹配问题。
  3. 方法逻辑优化:
    • setCurrentParcel支持部分字段更新,适配React Hook Form的字段级数据同步(比如通过watch或setValue更新单个字段)。
    • addParcelToList执行添加后自动重置当前包裹,提升用户操作体验。
    • removeParcelFromList通过唯一ID精准删除列表项,符合实际业务需求。
  4. 初始化值规范:使用明确的initialParcel作为初始值,避免Object.create({})带来的空对象潜在问题,确保必填字段有合法默认值。

原错误细节解释

  • 原代码中parcel是单个对象,却尝试调用数组的push方法,本身逻辑矛盾。
  • 原produce回调参数命名为parcel,但内部错误使用了未定义的draft变量,同时错误访问draft.parcel,触发变量未定义类错误,最终表现为draft is not a function。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:55:35