React useReducer 复杂状态
前置:React Context 跨层传值
目标:会写reducer(state, action);理解何时从useState升级。
引言
一个开关、一个输入框 → useState 最爽。
状态字段变多、更新规则互相牵扯时,一堆 setX / setY 容易乱。
useReducer:把「怎么从旧状态算出新状态」收进纯函数 reducer(state, action);组件里只 dispatch({ type: '...' })。
本篇 demo:计数器 + 可调步长 + 重置,四种 action。
动手
完整代码:react2/12-usereducer/demo
cd react2/12-usereducer/demo
npm install && npm run dev
改步长再点 +step / -step;点「重置」回到 count=0, step=1。
1. 定义 state / action / reducer
type State = { count: number; step: number }
type Action =
| { type: 'inc' }
| { type: 'dec' }
| { type: 'setStep'; step: number }
| { type: 'reset' }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'inc':
return { ...state, count: state.count + state.step }
case 'dec':
return { ...state, count: state.count - state.step }
case 'setStep':
return { ...state, step: action.step }
case 'reset':
return { count: 0, step: 1 }
default:
return state
}
}
约定:
- 纯函数:同样输入 → 同样输出;不写副作用
- 不可变更新:返回新对象,别直接改
state.count = ... - action.type 描述「发生了什么」,不是「把 count 设成几」
2. 组件里 dispatch
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 })
<button onClick={() => dispatch({ type: 'inc' })}>+step</button>
<button onClick={() => dispatch({ type: 'dec' })}>-step</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
<input
type="number"
value={state.step}
onChange={(e) =>
dispatch({ type: 'setStep', step: Number(e.target.value) || 1 })
}
/>
UI 只发意图;怎么改状态全在 reducer。
3. 何时从 useState 升级
| 继续 useState | 考虑 useReducer |
|---|---|
| 单个布尔 / 字符串 | 下一状态严重依赖上一状态 |
| 字段少、更新简单 | 同一状态有多种「动作」 |
| 想把更新逻辑抽到组件外测 |
useReducer + Context 常一起做中等复杂度的客户端状态;更大更碎的全局状态看 Zustand(15 篇)。
4. 常见坑
| 做法 | 问题 |
|---|---|
reducer 里 fetch / 改 DOM | 副作用放错地方;副作用进 event / effect |
直接 mutate state | React 可能检测不到更新 |
| action 设计成「setCount(5)」一堆 | 丢失「意图」;能工作但难推理 |
| 简单开关也上 reducer | 过度;useState 更短 |
demo 在练什么
- 手写
State/Action/reducer - 用
dispatch驱动多动作更新 - 体会「步长影响加减」这种跨字段规则放在 reducer 里更清晰
自己改一改:加 type: 'double';或把初始 state 换成惰性初始化 useReducer(reducer, undefined, init)。