先记住这个答案
多个简单受控字段可以通过 name 标识字段,事件处理器读取对应值,再用函数式更新构造下一份表单对象,例如保留旧字段并覆盖本次字段。文本通常读取 value,复选框读取 checked,多选需要 selectedOptions,文件需要 files,数字还应区分编辑字符串与业务数字。通用处理器的价值在于统一更新入口,字段专属的转换、校验和复杂交互仍应清楚保留。
- name 路由字段,函数式更新保留同一状态中的其它值
- 按控件语义读取,不能所有字段都用 value
- 复杂路径和业务规则需要明确适配,不靠字符串随意赋值
统一入口首先要保护其它字段
假设设置表单有 displayName、email 与通知开关,共用处理器可以先读取 name,再通过 setForm(previous => ({ ...previous, [name]: nextValue })) 更新本次字段。直接把新状态设成只有本字段的对象,会把其它字段一起丢掉;在并发更新中依赖旧闭包对象,也容易互相覆盖。
事件处理器可以在同步执行时先提取必要的字段名与值,再交给状态更新或异步校验。不要把整个 DOM 事件对象当成长期业务数据保存。name 最好来自明确字段定义,处理器也可验证允许的字段名,避免拼写错误悄悄创建一个界面不再读取的新属性。
按控件语义建立小而清楚的适配
复选框的 value 表示选项提交值,checked 才是勾选布尔;select multiple 需要读取所有 selectedOptions;文件输入提供 FileList,不能从字符串路径恢复文件。数字字段编辑阶段可以保留字符串,提交前再判断空值与范围,而不是在万能处理器里统一 Number 转换。
当字段差异逐渐增多,可以为字段提供小型取值函数或转换规则,由通用入口负责调用与提交。复杂日期区间、富文本和上传队列可能不产生原生 input 事件,直接接受明确业务值会比伪造一个 target 对象更容易理解。复用接口应服务于数据模型,而不是要求所有组件伪装成文本框。
嵌套数据和状态规模需要独立设计
name 写成 address.city,并不会让对象展开与计算属性自动更新 address 里的 city,它只会建立一个带点号的普通键。如果确实支持路径,应使用经过约束的路径解析与不可变更新方案,并检查原型相关特殊键;简单表单也可以用显式字段处理避免不必要的通用路径机制。
几十个字段是否存成一个对象,还要看共同提交、联动和渲染成本。集中状态便于统一提交,但不要求每次输入都重算全部校验或刷新所有昂贵区域。可以按步骤、字段订阅或局部组件组织更新,同时保持最终提交数据的权威来源清楚,避免每个输入各存一份无法统一重置的副本。
容易答错的地方
- 共用 onChange 就必须只有一个三元表达式
- 控件语义和业务转换可能确实不同。清楚的小分支或字段适配比不断嵌套的类型判断更易维护,抽象应该减少重复流程,而不是隐藏差异。
- 计算属性名天然支持任意嵌套字段
- 普通对象的计算属性只是写入一个键,不会解释点号、数组索引或路径权限。需要嵌套更新时必须明确解析规则,不能把不受约束的路径直接交给通用写入函数。
面试官还会怎么问?
多个快速更新为什么推荐函数式 setter?
下一份对象依赖此前字段时,更新函数能基于 React 提供的前一状态组合结果,减少旧闭包覆盖其它更新的风险。更新函数应保持纯粹,不要在其中启动请求或修改外部对象。
是否所有校验都应在 onChange 中完成?
不需要,轻量格式提示可以即时做,昂贵或异步校验应考虑触发时机、取消与迟到结果。提交仍需完成必要校验,不能把即时提示当作服务端数据可信保证。
什么时候改用 reducer 或表单库?
当字段转移有明确业务动作、联动复杂、需要统一脏状态和错误生命周期时,可以评估这些工具。字段数量只是信号,选择依据应是规则复杂度与维护需求,而不是达到某个数字就必须换方案。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。