在 React 中,最常用的状态管理 Hook 有三个:
- useState
- useRef
- useReducer
它们都能"存数据",但作用完全不同。
本文通过对比、代码示例和最佳实践,让你一眼看懂三者的异同点与使用策略。
1. 三者一句话总结(记住这个就够了)
| Hook | 特点 | 什么时候用 |
|---|---|---|
| useState | 会触发组件重新渲染 | UI 需要根据数据变化而更新 |
| useRef | 不会触发渲染,可持久存储 | 保存 DOM、保存不影响 UI 的数据、避免频繁渲染 |
| useReducer | 适合复杂状态逻辑,集中管理 | 多步骤状态、复杂更新规则、类似 Vuex/Redux |
2. useState ------ 最常用的 UI 状态管理方式
📌 用途
- 管理与 UI 显示相关的状态
- 一旦更新 → React 会重新渲染组件
📌 示例:计数器
jsx
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
👉 每次 setCount 运行,UI 都会更新。
3. useRef ------ 不触发渲染的"可变容器"
📌 用途
- 保存不会影响 UI 的值(计时器、缓存、临时变量)
- 保存 DOM 节点引用
- 在渲染周期之间持久化数据
📌 示例:保存一个不会影响 UI 的计数器
jsx
const counterRef = useRef(0);
function add() {
counterRef.current += 1;
console.log(counterRef.current);
}
return <button onClick={add}>Add</button>;
👉 按多少次,UI 都不会变化,因为它 不会触发重渲染。
4. useReducer ------ 多分支、复杂逻辑的状态管理
📌 用途
适合以下情况:
- 状态结构复杂(多字段)
- 更新逻辑复杂(多 if/else 或 switch)
- 想将逻辑分离,让代码更清晰
📌 示例:管理一个表单对象
jsx
function reducer(state, action) {
switch (action.type) {
case "setName":
return { ...state, name: action.payload };
case "setAge":
return { ...state, age: action.payload };
case "reset":
return { name: "", age: 0 };
default:
return state;
}
}
const [form, dispatch] = useReducer(reducer, {
name: "",
age: 0,
});
👉 适合"动作驱动"的状态结构。
5. 三者的核心区别(最关键)
| 对比点 | useState | useRef | useReducer |
|---|---|---|---|
| 是否触发渲染 | ✔ 会 | ❌ 不会 | ✔ 会 |
| 存储数据类型 | 简单/基本 | 任意 | 复杂对象 |
| 逻辑复杂度 | 简单 | 简单 | 中-高 |
| 适合多字段状态 | 不太合适 | 不合适 | ✔ 最合适 |
| 跨 render 保留值 | ✔ | ✔ | ✔ |
| 管理 DOM | ✘ | ✔ | ✘ |
| 适合封装业务逻辑 | 一般 | 否 | ✔ 非常好 |
6. 如何选择?(最实用的决策树)
🟦 1)数据是否影响 UI?
- 是 → useState 或 useReducer
- 否 → useRef
🟩 2)数据更新逻辑是否复杂?
- 复杂(多字段、多动作)→ useReducer
- 简单(一个值)→ useState
🟨 3)更新是否非常频繁?(例如输入法、mousemove)
- 是,但 UI 不依赖 → useRef
- 是,且 UI 要更新 → useState + 性能优化
🟧 4)是否需要类似 Redux 的写法?
- 是 → useReducer
- 否 → useState / useRef
7. 最容易犯的错误(务必注意)
❌ 把 useRef 当 useState 用
jsx
const count = useRef(0);
count.current++; // UI 不更新!
👉 你以为 UI 会变,但不会。
❌ 用 useState 处理频繁更新、但 UI 不需要的数据
例如 storing mousemove 坐标:
- 会造成大量 re-render,卡顿
- 推荐用 useRef
❌ 在 useReducer 中修改 state(不可变规则)
错误 ❌:
jsx
state.age = 10;
return state;
正确 ✔:
jsx
return { ...state, age: 10 };
8. 三者组合使用示例(真实项目中常见)
示例:表单组件
| 状态 | 用哪个? | 为什么 |
|---|---|---|
| 表单字段 | useReducer | 多字段、动作复杂 |
| 表单提交 loading | useState | 简单布尔值 |
| DOM 节点(input) | useRef | 保存 DOM |
| 防抖计时器 | useRef | 不触发渲染 |
9. 小结:最佳实践
| 场景 | 推荐 |
|---|---|
| UI 状态简单 | useState |
| UI 状态复杂、多字段 | useReducer |
| 数据不会用于渲染 | useRef |
| 保存 DOM 节点 | useRef |
| 保存缓存、前一次值 | useRef |
| 避免频繁 re-render | useRef |
| 需要统一管理 action | useReducer |
结语
useState、useRef、useReducer 都可以存储数据,但它们在 React 渲染机制中的角色完全不同。
- useState = UI 状态
- useRef = 自定义缓存 / DOM
- useReducer = 逻辑复杂的状态机
掌握好这三者的边界,就能写出结构清晰且性能优秀的 React 代码。