在 React 项目中,useState 已经很好用了,为什么还需要 useReducer?
尤其当你项目规模大起来,状态越来越多,一个页面要管理的东西越来越复杂时,useReducer 就能帮你把状态管理得更清晰、更可靠。
先看看 useState 有什么局限?
在小型组件里,useState 完全没问题:
const [count, setCount] = useState(0);
这时候,count 的更新逻辑很简单,直接 setCount 就完事儿了。
可当你的状态不仅仅是一个数字,而是一个对象、数组,或者需要多种修改方式时,useState 的逻辑就容易变得混乱。
useReducer 是什么?
可以把 useReducer 理解成一个小型的 Redux。
- 它是 React 自带的状态管理方案。
- 和
useState类似,都是用来管理响应式状态的。 - 当状态比较复杂,或者有多种修改方式时,
useReducer更清晰。
用一段代码来感受一下
下面是一个典型的 useReducer 用法示例:
import { useReducer, useState } from 'react';
const initialValue = { count: 0 };
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'incrementByNum':
return { count: state.count + action.payload };
default:
return state;
}
};
function App() {
const [state, dispatch] = useReducer(reducer, initialValue);
const [count, setCount] = useState(0);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<input
type="text"
value={count}
onChange={(e) => setCount(e.target.value)}
/>
<button onClick={() => dispatch({ type: 'incrementByNum', payload: Number(count) })}>
Increment By Number
</button>
</>
);
}
export default App;
🖼 useReducer 运行效果
👇 这是上面示例代码在浏览器中的实际效果:
你可以看到:
- 点击
+/-可以增加或减少Count - 输入框可以输入任意数字,然后点击
Increment By Number,一次性加上指定的值
这段代码做了什么?
-
状态初始值
const initialValue = { count: 0 };用一个对象
initialValue定义了状态的初始结构,后续每次reducer处理时,都会以它为基础。 -
用纯函数
reducer管理所有状态修改规则const reducer = (state, action) => { ... }state:表示当前的状态数据,是组件界面渲染的依据,只要state变化,React 就会自动重新渲染 UI。action:是一个对象,用来描述需要对状态做什么操作,它必须带一个type字段来指定操作类型(比如"increment"、"decrement"),有时还会带额外的数据(如payload),用于携带修改所需的信息。
👉
reducer的作用就像一个状态工厂,它根据action的类型和数据,返回新的state,整个过程是纯函数、没有副作用。 -
useReducer返回[state, dispatch]const [state, dispatch] = useReducer(reducer, initialValue);state:就是最新的状态值,组件每次渲染都会用到它。dispatch:是一个函数,用来派发 action。你不能直接改state,而是必须通过dispatch传递一个action给reducer,由它来生成新的状态,保证了状态管理的统一和可预测性。
例如:
dispatch({ type: 'increment' }) // 表示执行加1 dispatch({ type: 'incrementByNum', payload: 5 }) // 表示按指定数值加这种**“状态只能被派发 action 改变”**的机制,是大型应用里保持状态一致性和可维护性的关键。
为什么要用 纯函数 管理状态?
reducer 函数必须是纯函数,这是状态可预测的关键。
纯函数的特点:
- 相同输入一定返回相同输出。
- 不修改外部变量,不操作 DOM,不发请求,不产生副作用。
例如:
function add(a, b) {
return a + b; // 纯函数
}
let total = 0;
function addToTotal(a) {
total += a; // 不纯,修改了外部变量
return total;
}
在状态管理中,如果 reducer 有副作用,就会让状态变得不可预测,Bug 很难排查。所以要坚持纯函数的原则!
再大一点,可以和 useContext 搭配使用
当状态要在跨层级组件中使用时,就需要全局共享。这时候可以:
<LoginContext.Provider>
<ThemeContext.Provider>
<TodosContext.Provider>
...
</TodosContext.Provider>
</ThemeContext.Provider>
</LoginContext.Provider>
但是嵌套太多时,管理也会变得复杂,这就是为什么有些大型项目还会选择 Redux。
总结
useReducer是useState的升级版,适用于复杂状态。reducer必须是纯函数,保证状态可预测。- 搭配
useContext可以实现跨组件全局状态管理。 - 当你感觉
useState管不过来了,不妨试试useReducer。
推荐实践
- 单个状态:
useState - 复杂对象状态:
useReducer - 跨层级全局状态:
useContext + useReducer或 Redux
希望这篇文章能帮你更好地理解 useReducer,别忘了,写好 reducer 的核心就是:简单、纯粹、可预测!
如果觉得有用,点个赞 👍 收藏起来,下次写状态管理时不迷路!🚀✨