如何在React-Hook-Form表单中用Immer更新Zustand包裹列表状态?
问题解决方案
核心问题分析
你遇到的draft is not a function错误,本质是两个关键问题导致的:
- 状态结构设计错误:原store中
parcel定义为单个SingleParcel对象,但业务需求是存储包裹列表,类型不匹配导致无法执行数组push操作。 - 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 } }); }, }));
关键修改说明
- 状态拆分:将原单一的
parcel拆分为currentParcel(存储当前编辑的单个包裹)和parcels(存储已保存的包裹列表),完全匹配"输入单个包裹→加入列表→继续添加"的业务流程。 - Immer正确用法:
produce的回调参数是整个状态的草稿对象draft,直接操作draft上的属性即可完成不可变更新,避免了原代码中的变量名错误和类型不匹配问题。 - 方法逻辑优化:
setCurrentParcel支持部分字段更新,适配React Hook Form的字段级数据同步(比如通过watch或setValue更新单个字段)。addParcelToList执行添加后自动重置当前包裹,提升用户操作体验。removeParcelFromList通过唯一ID精准删除列表项,符合实际业务需求。
- 初始化值规范:使用明确的
initialParcel作为初始值,避免Object.create({})带来的空对象潜在问题,确保必填字段有合法默认值。
原错误细节解释
- 原代码中
parcel是单个对象,却尝试调用数组的push方法,本身逻辑矛盾。 - 原
produce回调参数命名为parcel,但内部错误使用了未定义的draft变量,同时错误访问draft.parcel,触发变量未定义类错误,最终表现为draft is not a function。
内容的提问来源于stack exchange,提问作者Christer Johansson
相关产品推荐
相关产品推荐

