useState 与 useReducer 该怎么选?别只会无脑用 useState

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 了?"

相关推荐
乐橙开放平台1 小时前
物业 SaaS 笔记:子账号 Policy 按通道隔离,At_ 管控面 / St_ 数据面治理 accessToken
后端·物联网·音视频
站大爷IP1 小时前
Python的is和==把我坑惨了,原来对象比较的水这么深
后端
fatcoder1 小时前
玩转Nginx 04 — 反向代理:给 nginx 接上后端
前端·后端·nginx
雨落倾城夏未凉1 小时前
halcon核心-颜色识别/颜色控件转换(十)
后端
SomeB1oody1 小时前
【RustyML入门】5.2. 分类指标
开发语言·后端·机器学习·rust·教程
明月_清风3 小时前
Pi Agent 深度解析:开源极简终端 AI 编码代理的终极指南
前端·后端·ai编程
程序员cxuan3 小时前
DeepSeek Harness 必装的插件公布了!
人工智能·后端·程序员
fatcoder3 小时前
玩转Nginx 03 — location 匹配规则:让不同的路径各回各家
前端·后端·nginx
wno7043 小时前
Spring Boot JdbcTemplate配置Druid多数据源
java·spring boot·后端