React 复杂表单状态机:用 useReducer 管理多步向导与联动校验
test12026-09-241 次阅读
为什么多步表单不适合纯 useState
一个三步注册向导(账号、资料、确认)如果拆成十几个 useState,步骤切换、字段联动、整页重置都散落在各处,组件很快变成一团难以追踪的状态。当某个字段变化需要同时影响多个派生值时,纯 useState 会出现"先 setA 再 setB"的时序问题。useReducer 把"当前状态 + 动作"收敛成纯函数,所有变更都走同一入口,便于推理和测试。
定义状态与动作
先用类型约束住 state 与 action,再用一个 reducer 描述每种动作如何产生新 state。下面是一段可直接放进项目的实现。
type FormState = {
step: number;
account: { email: string; password: string };
profile: { nickname: string; city: string };
errors: Record<string, string>;
};
type Action =
| { type: "SET_FIELD"; section: "account" | "profile"; key: string; value: string }
| { type: "NEXT" }
| { type: "PREV" }
| { type: "SET_ERRORS"; errors: Record<string, string> }
| { type: "RESET" };
const initial: FormState = {
step: 0,
account: { email: "", password: "" },
profile: { nickname: "", city: "" },
errors: {},
};
function reducer(state: FormState, action: Action): FormState {
switch (action.type) {
case "SET_FIELD":
return {
...state,
[action.section]: { ...state[action.section], [action.key]: action.value },
errors: { ...state.errors, [action.key]: "" },
};
case "NEXT":
return { ...state, step: Math.min(state.step + 1, 2) };
case "PREV":
return { ...state, step: Math.max(state.step - 1, 0) };
case "SET_ERRORS":
return { ...state, errors: action.errors };
case "RESET":
return initial;
default:
return state;
}
}
在组件里驱动
组件只负责派发动作,校验逻辑也可放进一个纯函数,校验失败时把错误写回 reducer,而不是各自维护零散的 error state。
import { useReducer } from "react";
function validateAccount(s: FormState): Record<string, string> {
const e: Record<string, string> = {};
if (!/^\S+@\S+$/.test(s.account.email)) e.email = "邮箱格式不正确";
if (s.account.password.length < 8) e.password = "密码至少 8 位";
return e;
}
export function Wizard() {
const [state, dispatch] = useReducer(reducer, initial);
const goNext = () => {
if (state.step === 0) {
const e = validateAccount(state);
if (Object.keys(e).length) return dispatch({ type: "SET_ERRORS", errors: e });
}
dispatch({ type: "NEXT" });
};
return (
<div>
<p>当前步骤:{state.step + 1}/3</p>
{state.errors.email && <p>{state.errors.email}</p>}
<button onClick={() => dispatch({ type: "PREV" })}>上一步</button>
<button onClick={goNext}>下一步</button>
<button onClick={() => dispatch({ type: "RESET" })}>重置</button>
</div>
);
}
什么时候该用
- 字段多、有步骤或明显状态流转(草稿、提交中、成功、失败)时,useReducer 让转移规则集中可控。
- 多个字段互相联动、或一处操作要改多处派生值时,纯函数 reducer 避免了 "set 顺序" 陷阱。
- 只是单个输入框,useState 更直观;不要为了用而用。
T
test1
文章作者
为什么多步表单不适合纯 useState 一个三步注册向导(账号、资料、确认)如果拆成十几个 useState,步骤切...
- 分类
- 技术
- 发布时间
- 2026-09-24
- 字数
- 约 2641 字
- 阅读
- 1 次
