React 表单状态管理经验总结:复杂表单不该一路 useState 写到底
React 里最容易写、也最容易写乱的东西之一,就是表单。
刚开始做简单输入框时,useState 足够直接。但一旦表单里出现下面这些需求,复杂度会迅速上升:
- 动态字段
- 联动校验
- 草稿保存
- 分步提交
- 错误提示
- 异步校验
这时如果还坚持“一个字段一个 useState”,代码很快就会失控。
最先出问题的通常不是校验,而是状态结构
很多表单越来越难维护,并不是因为规则复杂,而是因为状态没有分层。
一个更清晰的做法通常是把表单拆成三类状态:
- 表单值
- 校验状态
- 提交流程状态
例如:
1const [values, setValues] = useState({ 2 name: '', 3 email: '', 4 company: '' 5}); 6 7const [errors, setErrors] = useState({}); 8const [isSubmitting, setIsSubmitting] = useState(false);
如果把这些状态混在一起,后面几乎必然出现“改一个字段,顺手影响整个提交流程”的耦合问题。
表单值适合集中,交互状态适合分开
values 可以集中管理,因为它们本质上属于一份数据快照。
但像下面这些状态,不建议都塞进一个大对象里:
- 当前是否提交中
- 哪个字段正在异步校验
- 是否显示确认弹窗
- 当前步骤是否可前进
这些属于流程或 UI 状态,和表单值不是一个层次。
复杂表单更适合 reducer
当字段很多、更新动作明确时,useReducer 往往比一堆 setState 更稳。
1function formReducer(state, action) { 2 switch (action.type) { 3 case 'update_field': 4 return { 5 ...state, 6 values: { 7 ...state.values, 8 [action.name]: action.value 9 } 10 }; 11 case 'set_errors': 12 return { 13 ...state, 14 errors: action.errors 15 }; 16 case 'set_submitting': 17 return { 18 ...state, 19 isSubmitting: action.value 20 }; 21 default: 22 return state; 23 } 24}
reducer 的价值不只是“统一写法”,而是把状态变化变成显式动作,后面调试和扩展都会更容易。
校验不要写成“哪里触发就在哪里拼”
表单校验最容易烂掉的方式,是在 onChange、onBlur、onSubmit 里各写一份条件分支。
更靠谱的方式是:
- 把校验逻辑提成单独函数
- 明确区分字段级校验和表单级校验
- 不同触发时机复用同一套规则
例如:
1function validate(values) { 2 const errors = {}; 3 4 if (!values.name.trim()) { 5 errors.name = '姓名不能为空'; 6 } 7 8 if (!values.email.includes('@')) { 9 errors.email = '邮箱格式不正确'; 10 } 11 12 return errors; 13}
这样至少能保证规则只有一个来源,而不是散落在多个事件里各写一份。
异步校验一定要考虑竞态
很多表单会在输入用户名、邮箱、邀请码时做异步校验。
这类场景最容易出现的问题是:
- 用户输入 A,发请求
- 很快又输入 B,再发请求
- A 的结果后回来,把 B 的结果覆盖了
所以异步校验至少要考虑:
- 请求取消
- 只处理最新一次响应
- 加上最小防抖
否则你看到的并不是“校验结果”,而是“返回顺序”。
提交流程不要只维护一个 loading
很多表单最后只有一个 loading 状态,这在简单场景没问题,但复杂一点就不够了。
例如提交过程里可能有:
- 本地校验
- 图片上传
- 主表提交
- 成功回跳
这些阶段都叫 loading,等于没有信息。
更实际的做法是用阶段状态来表达:
idlevalidatinguploadingsubmittingsuccesserror
一旦流程出问题,你会更容易定位卡在哪一步。
什么时候该用表单库
如果你的表单有这些特征:
- 字段数量多
- 规则复杂
- 需要和 UI 组件库深度整合
- 多个页面都有类似模式
那引入表单库通常是合理的。
但核心依然不是“为了用库而用库”,而是你是否已经清楚:
- 状态怎么分层
- 校验怎么组织
- 提交流程怎么表达
如果这些都没想清楚,换什么库也只是在换一种混乱方式。
写在最后
复杂表单的难点从来不只是输入和提交,而是状态、规则和流程三者的组织方式。
在 React 里,表单写得稳不稳,关键不在于用了多少 Hook,而在于你有没有把“数据”“校验”“流程”分开建模。只要这一步做对,后面的代码会清爽很多。