useReducer 和 useState 有什么区别?适合什么场景?
简化版
useState 适合简单、独立的状态;useReducer 适合状态更新逻辑复杂、多个字段联动、下一状态依赖动作类型的场景。useReducer 把更新逻辑集中到 reducer 函数里,代码更像“状态机”,也更方便测试和维护。
详细版
useState 写法直接:
const [count, setCount] = useState(0);
setCount(count + 1);
useReducer 把更新逻辑放到 reducer:
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'increment' });
如果只是一个数字开关,用 useState;如果是表单、多步骤流程、购物车、复杂筛选条件,useReducer 更清晰。
完整版教学
一、useReducer 解决复杂状态更新分散的问题
组件状态简单时,useState 很直接。
但当一个组件有多个相关字段,并且更新逻辑分散在多个事件处理函数里,维护成本会快速上升。
useReducer 的目标是把“怎么从旧状态得到新状态”的逻辑集中到一个函数中。
const [form, setForm] = useState({
name: '',
age: 0,
touched: false,
errors: {},
});
如果表单有 8 个字段、5 种交互、3 类校验,事件处理函数里会出现很多 setForm({...})。
这些更新分散后,想判断某个字段为什么变了就很麻烦。
useReducer 让变化统一经过 dispatch(action),读代码时先看 action 类型,再看 reducer 分支。
二、useState 更适合简单独立状态
useState 的优势是轻量、直接、学习成本低。
对于开关、输入框值、当前页码、简单 loading 状态,用 useState 反而更清楚。
不要为了显得高级而把所有状态都改成 reducer。
const [visible, setVisible] = useState(false);
const [keyword, setKeyword] = useState('');
const [page, setPage] = useState(1);
这 3 个状态如果彼此没有复杂联动,拆成 3 个 useState 没问题。
面试回答里要体现判断标准:状态是否复杂、更新是否联动、逻辑是否需要集中。
如果只是 visible = !visible,写 reducer 反而增加样板代码。
三、reducer 是纯函数,输入旧状态和动作,返回新状态
reducer 的核心签名是 (state, action) => nextState。
它不应该直接修改原 state,也不应该在里面发请求、改 DOM、写 localStorage。
这样 reducer 才容易测试和推理。
type State = { count: number };
type Action = { type: 'add'; payload: number } | { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'add':
return { count: state.count + action.payload };
case 'reset':
return { count: 0 };
}
}
数字例子:state.count = 2,action 是 { type: 'add', payload: 3 },新状态就是 { count: 5 }。
这个推导完全由输入决定,不依赖外部环境。
这就是 reducer 容易单元测试的原因。
四、useReducer 让动作语义比直接 setState 更清楚
setState 表达的是“把状态改成什么”。
dispatch 表达的是“发生了什么动作”。
在复杂业务里,动作语义往往更重要。
dispatch({ type: 'submit_success', payload: result });
dispatch({ type: 'submit_failed', payload: error });
dispatch({ type: 'field_changed', field: 'email', value: 'a@b.com' });
| 对比项 | useState | useReducer |
|---|---|---|
| 更新方式 | 直接给新值或更新函数 | dispatch action |
| 适合状态 | 简单独立 | 复杂联动 |
| 逻辑位置 | 分散在事件里 | 集中在 reducer |
| 测试便利性 | 一般 | reducer 可单测 |
| 样板代码 | 少 | 更多 |
这张表能帮你避免把两者说成“谁替代谁”。
五、useReducer 常和 Context 搭配管理局部复杂状态
当一个页面有多层组件都需要读状态和触发更新时,可以把 useReducer 和 Context 结合。
父组件用 reducer 管状态,子组件通过 Context 拿 state 和 dispatch。
这种方案适合中等复杂度,不一定需要 Redux 或 Zustand。
const StateContext = createContext(null);
const DispatchContext = createContext(null);
function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
流程如下:
子组件点击按钮
-> dispatch(action)
-> reducer 计算 nextState
-> Provider 重新渲染
-> 消费 state 的组件更新
这比层层传 setXxx 更清楚。
六、useReducer 不是全局状态管理库
useReducer 只是 React 内置 Hook。
它没有开箱即用的 devtools、持久化、中间件、跨页面 store 组织能力。
如果状态是全应用共享、需要时间旅行调试、需要复杂缓存策略,仍然要考虑专业状态库或服务端状态方案。
适合 useReducer:
单页面复杂表单、多步骤向导、局部状态机
不适合只靠 useReducer:
大型全局 store、服务端缓存、跨标签页同步
记忆钩子:
useState管“一个值怎么变”,useReducer管“一组状态因什么动作而变”。
面试时补上这个边界,会显得你不是只背 API,而是懂工程选型。
七、常见误区与追问
- 误区:useReducer 一定比 useState 高级。 简单状态用
useState更直接,useReducer适合复杂联动和动作语义明确的场景。 - 误区:reducer 里可以直接发请求。 reducer 应保持纯函数,异步请求应放在事件、Effect 或 action 创建逻辑外层。
- 误区:useReducer 就等于 Redux。 两者都用 reducer 思想,但 Redux 是完整状态管理库,
useReducer只是组件内 Hook。 - 追问:useReducer 怎么初始化复杂状态? 可以传第三个 lazy initializer 参数,把昂贵初始化逻辑延迟执行。
- 追问:为什么 reducer 更容易测试? 因为它输入旧 state 和 action,输出新 state,不依赖 React 渲染环境。
- 追问:dispatch 会不会立刻改变当前 state 变量? 不会,和
setState一样,更新会进入 React 调度,当前渲染闭包里的 state 不会马上变。
八、加强记忆
这题按“简单状态用 useState,复杂状态用 useReducer”展开。核心不是语法,而是 reducer 把更新逻辑集中成纯函数,通过 action 表达发生了什么;再补充它常和 Context 搭配,但不等于 Redux,也不适合包办所有全局状态。