useState 与 useReducer 该怎么选?别只会无脑用 useState
"这个状态用 useState 就行吧?"
"状态一多我就多写几个 useState,有啥问题?"
"useReducer 不就是 Redux 的降级版吗,我用不上。"
这是很多 React 开发者(甚至工作一两年的)真实的心态。
useState 用起来简单,但也最容易用"滥"。
useReducer 看起来复杂,但往往是代码变复杂时的"解药"。
今天这篇文章,我们就把 useState 和 useReducer 的选型问题讲透。
一、先给结论(省流版)
| 场景 | 推荐 |
|---|---|
| 单个布尔值、数字、字符串 | ✅ useState |
| 状态之间互不影响 | ✅ useState |
| 表单字段(简单) | ✅ useState |
| 多个状态相互关联 | ✅ useReducer |
| 下一个状态依赖前一个状态 | ✅ useReducer |
| 状态逻辑复杂、难维护 | ✅ useReducer |
| 需要复用状态逻辑 | ✅ useReducer |
| 类似 Redux 的清晰数据流 | ✅ useReducer |
一句话总结:
useState 适合"简单状态",useReducer 适合"复杂状态逻辑"。
二、useState 的本质:分散的原子状态
useState 的典型用法
scss
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
useState 的优点
✅ 心智负担低
✅ 上手快
✅ 写起来爽
useState 的隐性问题
当你开始这样写的时候,问题就来了:
scss
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const [isRunning, setIsRunning] = useState(false);
const [history, setHistory] = useState([]);
问题不在代码能不能跑,而在"逻辑是否分散"。
三、useReducer 的本质:状态 + 行为 = 集中管理
useReducer 的典型用法
arduino
const initialState = {
count: 0,
step: 1,
isRunning: false,
history: []
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
...state,
count: state.count + state.step,
history: [...state.history, state.count]
};
case 'setStep':
return { ...state, step: action.payload };
case 'toggle':
return { ...state, isRunning: !state.isRunning };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, initialState);
useReducer 的核心价值
✅ 状态逻辑集中
✅ 状态变化可预测
✅ action 就是"变更文档"
✅ 天然符合 Redux 思维
四、什么时候 useState 开始"反噬"你?
1️⃣ 状态之间相互依赖
scss
// ❌ useState 写法
setCount(count + step);
setHistory([...history, count]);
问题:
- 依赖顺序
- 依赖闭包
- 容易出 bug
✅ useReducer 写法
bash
dispatch({ type: 'increment' });
👉 所有逻辑都在 reducer 里,一目了然
2️⃣ 同一个事件修改多个状态
scss
// ❌ useState
setLoading(true);
setError(null);
setData(null);
fetchData().then(res => {
setData(res);
setLoading(false);
});
✅ useReducer
ini
dispatch({ type: 'fetch_start' });
fetchData().then(res => {
dispatch({ type: 'fetch_success', payload: res });
});
reducer 中统一描述"状态如何变化",组件只负责"发生了什么"。
3️⃣ 组件越来越臃肿
当你发现:
- 一个组件里有 6 个
useState - 一个事件处理函数里改 4 个状态
- 新增需求要同时改多处状态逻辑
👉 这是 useReducer 的最佳切入点
五、useReducer 并不是"重量级"方案
很多新手觉得 useReducer "太重",其实不然。
对比一下代码量
useState 版本(逻辑分散)
ini
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const increment = () => {
setCount(c => c + step);
};
const changeStep = e => {
setStep(Number(e.target.value));
};
useReducer 版本(逻辑集中)
arduino
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'changeStep':
return { ...state, step: action.payload };
default:
return state;
}
}
逻辑并没有变多,只是从"散落各处"变成了"集中管理"。
六、useReducer 的隐藏优势
✅ 1. 更容易测试
reducer 是 纯函数:
less
expect(reducer(state, { type: 'increment' })).toEqual(newState);
不需要 React,不需要渲染组件。
✅ 2. 更容易复用
scss
function useCounter(reducer) {
return useReducer(reducer, initialState);
}
多个组件共享同一套状态逻辑。
✅ 3. 更容易维护(尤其是多人协作)
bash
dispatch({ type: 'submit_form' });
比一堆 setXxx 更语义化。
七、一个真实案例:表单状态
❌ useState 表单(常见新手写法)
scss
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const [submitting, setSubmitting] = useState(false);
✅ useReducer 表单(推荐)
csharp
function formReducer(state, action) {
switch (action.type) {
case 'change':
return {
...state,
values: {
...state.values,
[action.field]: action.value
},
errors: {
...state.errors,
[action.field]: null
}
};
case 'submit':
return { ...state, submitting: true };
case 'success':
return initialState;
case 'error':
return { ...state, errors: action.errors, submitting: false };
default:
return state;
}
}
表单越复杂,useReducer 的优势越明显。
八、那我是不是应该"全用 useReducer"?
❌ 不建议。
过度使用 useReducer 的问题
- 简单状态变复杂
- 样板代码增多
- 可读性下降
一个简单判断标准
如果一个状态变化不需要"解释",就用 useState
如果一个状态变化需要"描述流程",就用 useReducer
九、官方态度是什么?
React 官方文档其实已经说得很清楚了:
"useReducer 是 useState 的替代方案,适用于复杂状态逻辑。"
并且:
useState本质是 useReducer 的语法糖- React 内部很多地方就是用 reducer 实现的
十、一张决策图(记这张就够了)
markdown
状态是否复杂?
├─ 否 → useState
└─ 是 → 状态是否相互关联?
├─ 否 → useState
└─ 是 → useReducer
十一、一句话总结
useState 是"点",useReducer 是"面"。
点多了,就该连成面。
别让 useState 成为你组件复杂度的天花板。
十二、写在最后
很多 React 开发者卡在"会用"和"用好"之间。
useState 让你入门,useReducer 让你进阶。
下次当你准备写第 5 个 useState 时,不妨停下来问自己一句:
"这些状态,是不是该用 useReducer 了?"