如何用 useReducer 管理复杂状态?理解纯函数和 React 状态管理的核心

53 阅读4分钟

在 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 运行效果

👇 这是上面示例代码在浏览器中的实际效果:

67tool-1752577352618.gif

你可以看到:

  • 点击 + / - 可以增加或减少 Count
  • 输入框可以输入任意数字,然后点击 Increment By Number,一次性加上指定的值

这段代码做了什么?

  1. 状态初始值

    const initialValue = { count: 0 };
    

    用一个对象 initialValue 定义了状态的初始结构,后续每次 reducer 处理时,都会以它为基础。

  2. 用纯函数 reducer 管理所有状态修改规则

    const reducer = (state, action) => { ... }
    
    • state:表示当前的状态数据,是组件界面渲染的依据,只要 state 变化,React 就会自动重新渲染 UI。
    • action:是一个对象,用来描述需要对状态做什么操作,它必须带一个 type 字段来指定操作类型(比如 "increment""decrement"),有时还会带额外的数据(如 payload),用于携带修改所需的信息。

    👉 reducer 的作用就像一个状态工厂,它根据 action 的类型和数据,返回新的 state,整个过程是纯函数、没有副作用。

  3. useReducer 返回 [state, dispatch]

    const [state, dispatch] = useReducer(reducer, initialValue);
    
    • state:就是最新的状态值,组件每次渲染都会用到它。
    • dispatch:是一个函数,用来派发 action。你不能直接改 state,而是必须通过 dispatch 传递一个 actionreducer,由它来生成新的状态,保证了状态管理的统一和可预测性。

    例如:

    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。


总结

  • useReduceruseState 的升级版,适用于复杂状态。
  • reducer 必须是纯函数,保证状态可预测。
  • 搭配 useContext 可以实现跨组件全局状态管理。
  • 当你感觉 useState 管不过来了,不妨试试 useReducer

推荐实践

  • 单个状态:useState
  • 复杂对象状态:useReducer
  • 跨层级全局状态:useContext + useReducer 或 Redux

希望这篇文章能帮你更好地理解 useReducer,别忘了,写好 reducer 的核心就是:简单、纯粹、可预测!

如果觉得有用,点个赞 👍 收藏起来,下次写状态管理时不迷路!🚀✨