如何使用useReducer更新状态中的嵌套对象字段?
问题根因
现有reducer仅实现了顶层属性的浅合并,传入{ user: { firstName: 输入值 } }作为更新参数时,会将整个user字段直接替换为新传入的对象,原user下的lastName、jobTitle等字段没有被合并到新对象中,因此会被覆盖丢失。
实现方案
方案1:改造reducer做嵌套层合并(表单场景优先选)
这个方案不需要为每个表单独写更新函数,所有输入框可以复用同一套更新逻辑,且状态合并在reducer内部完成,不会出现闭包拿旧状态的问题。
首先调整reducer的类型定义和逻辑:
// 定义reducer接收的更新动作类型,类型安全约束字段范围 type RegisterUpdateAction = { field: keyof RegisterReq['user']; value: string; }; const registerReducer = (state: RegisterReq, action: RegisterUpdateAction): RegisterReq => { return { ...state, user: { // 先保留原有user下的所有字段 ...state.user, // 仅覆盖本次要更新的指定字段 [action.field]: action.value } }; };
然后写通用的输入框变更处理函数,给所有表单项共用:
const handleFormInputChange: React.ChangeEventHandler<HTMLInputElement> = (evt) => { // 在input标签上通过data-field属性绑定对应字段名,比如data-field="firstName" const targetField = evt.target.dataset.field as keyof RegisterReq['user']; updateRegister({ field: targetField, value: evt.target.value }); };
表单输入框用法示例:
<input type="text" data-field="firstName" value={registerReq.user.firstName} onChange={handleFormInputChange} placeholder="请输入名" /> <input type="text" data-field="lastName" value={registerReq.user.lastName} onChange={handleFormInputChange} placeholder="请输入姓" /> <input type="email" data-field="email" value={registerReq.user.email} onChange={handleFormInputChange} placeholder="请输入邮箱" />
方案2:不修改reducer,更新时手动合并嵌套字段
如果不想调整现有reducer结构,调用更新函数时手动展开原有嵌套对象的属性再覆盖目标字段即可,缺点是每个字段都要重复写展开逻辑,且容易因为闭包拿到旧状态导致更新异常:
const updateFirstName: React.ChangeEventHandler<HTMLInputElement> = (evt) => { updateRegister({ user: { // 手动展开原有user的全部属性 ...registerReq.user, // 覆盖要更新的字段 firstName: evt.target.value } }) };
注意:该方案依赖渲染闭包中拿到的
registerReq状态值,如果同一执行周期内触发多次状态更新,可能拿到过期的状态值导致更新被覆盖,生产场景不推荐使用。
内容的提问来源于stack exchange,提问作者Ryan Speciale
相关产品推荐
相关产品推荐

