React中useReducer和useState的实现示例

 更新时间:2026年08月17日 09:47:15   作者:JJCTO袁龙  
本文主要介绍了React中useReducer和useState的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一句话总结:状态简单用一个变量就能搞定 → useState;状态有关联关系、更新逻辑复杂、或需要「状态机」模式 → useReducer。别把简单问题搞复杂,也别把复杂状态硬塞进一堆 useState!

一、useReducer 和 useState 的核心区别

// useState:一个状态变量
const [count, setCount] = useState(0);
setCount(10);
setCount(prev => prev + 1);

// useReducer:状态 + 派发 action
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'increment' });
dispatch({ type: 'set', payload: 10 });
对比项useStateuseReducer
适用状态单个独立值多个关联值 / 状态机
更新方式直接设值或函数式更新派发 action,reducer 集中处理
更新逻辑分散在组件各处集中在 reducer 函数
可测试性差(逻辑在组件内)好(reducer 是纯函数)
学习成本低中
适用场景简单状态复杂状态、表单、步骤流

一句话:useState 是「值 + setter」,useReducer 是「状态 + action + reducer」。状态越复杂,useReducer 的优势越明显。

二、5 大高频场景 & 代码对比

① 表单状态:5 个 useState vs 1 个 useReducer

// ❌ 5 个 useState,更新逻辑分散
function LoginForm() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});
  const [isLoading, setIsLoading] = useState(false);
  const [isSubmitted, setIsSubmitted] = useState(false);

  const handleSubmit = async () => {
    setIsLoading(true);
    setErrors({});
    try {
      await login(username, password);
      setIsSubmitted(true);
    } catch (e) {
      setErrors({ form: e.message });
    } finally {
      setIsLoading(false);
    }
  };

  // 每个字段都要单独写 setter...
}

修复:用 useReducer 集中管理

// ✅ useReducer 集中管理,逻辑清晰
const initialState = {
  username: '',
  password: '',
  errors: {},
  isLoading: false,
  isSubmitted: false,
};

function formReducer(state, action) {
  switch (action.type) {
    case 'SET_FIELD':
      return { ...state, [action.field]: action.value };
    case 'SET_ERRORS':
      return { ...state, errors: action.errors };
    case 'START_SUBMIT':
      return { ...state, isLoading: true, errors: {} };
    case 'SUBMIT_SUCCESS':
      return { ...state, isLoading: false, isSubmitted: true };
    case 'SUBMIT_ERROR':
      return { ...state, isLoading: false, errors: action.errors };
    default:
      return state;
  }
}

function LoginForm() {
  const [state, dispatch] = useReducer(formReducer, initialState);

  const handleSubmit = async () => {
    dispatch({ type: 'START_SUBMIT' });
    try {
      await login(state.username, state.password);
      dispatch({ type: 'SUBMIT_SUCCESS' });
    } catch (e) {
      dispatch({ type: 'SUBMIT_ERROR', errors: { form: e.message } });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={state.username}
        onChange={e => dispatch({ type: 'SET_FIELD', field: 'username', value: e.target.value })}
      />
      {/* ... */}
    </form>
  );
}

② 步骤流 / 状态机:多个状态互相关联

// ❌ 多个 useState 管理步骤流,状态组合可能不合法
function Checkout() {
  const [step, setStep] = useState(1);
  const [cart, setCart] = useState([]);
  const [address, setAddress] = useState(null);
  const [payment, setPayment] = useState(null);

  // ❌ 可能出现 step=3 但 address=null 的非法状态
  const nextStep = () => {
    if (step === 1 && cart.length === 0) return;
    setStep(step + 1);   // ❌ 各状态独立,容易出现非法组合
  };
}

修复:用 useReducer 保证状态转换的合法性

// ✅ useReducer 集中管理,状态转换有约束
const initialState = {
  step: 'cart',      // cart → address → payment → confirm
  cart: [],
  address: null,
  payment: null,
};

function checkoutReducer(state, action) {
  switch (action.type) {
    case 'ADD_TO_CART':
      return { ...state, cart: [...state.cart, action.item] };

    case 'GO_TO_ADDRESS':
      if (state.cart.length === 0) return state;   // ✅ 购物车空不能下一步
      return { ...state, step: 'address' };

    case 'SET_ADDRESS':
      return { ...state, address: action.address, step: 'payment' };

    case 'SET_PAYMENT':
      return { ...state, payment: action.payment, step: 'confirm' };

    case 'BACK':
      // ✅ 回退逻辑集中管理
      const order = { cart: 'address', address: 'cart', payment: 'address', confirm: 'payment' };
      return { ...state, step: order[state.step] };

    default:
      return state;
  }
}

③ 多个 setState 连续调用,useState 批处理容易出错

// ❌ 多个 setState 连续调用,依赖前一个的值
function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);
  const [total, setTotal] = useState(0);

  const handleIncrement = () => {
    setCount(c => c + step);        // ❌ 依赖 step
    setTotal(t => t + step);        // ❌ 逻辑分散
    if (count + step > 10) {
      setStep(1);                   // ❌ 条件判断依赖旧 count
    }
  };
}

修复:useReducer 一次 dispatch 搞定

// ✅ 一次 dispatch,状态原子更新
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      const newCount = state.count + state.step;
      return {
        count: newCount,
        step: newCount > 10 ? 1 : state.step,   // ✅ 集中判断
        total: state.total + state.step,
      };
    case 'SET_STEP':
      return { ...state, step: action.step };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, {
    count: 0, step: 1, total: 0,
  });

  const handleIncrement = () => dispatch({ type: 'INCREMENT' });  // ✅ 一行搞定
}

④ 简单计数器却用了 useReducer(过度设计)

// ❌ 简单计数器用 useReducer,杀鸡用牛刀
function Counter() {
  const [state, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'increment': return state + 1;
      case 'decrement': return state - 1;
      case 'reset': return 0;
      default: return state;
    }
  }, 0);

  return (
    <div>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      {state}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
}

修复:简单状态用 useState

// ✅ 简单状态直接 useState
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c - 1)}>-</button>
      {count}
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

⑤ useReducer 传给子组件,避免 prop drilling

// ❌ 父组件用 useReducer,但 dispatch 逐层传递
function Parent() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return <Child dispatch={dispatch} />;   // ❌ 如果层级深,一层层传
}

function Child({ dispatch }) {
  return <GrandChild dispatch={dispatch} />;   // ❌ prop drilling
}

修复:用 Context 共享 dispatch

// ✅ 用 Context 共享 state + dispatch
import { createContext, useContext, useReducer } from 'react';

const StoreContext = createContext(null);

function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
}

// 任意子组件直接用
function DeepChild() {
  const { state, dispatch } = useContext(StoreContext);
  return <button onClick={() => dispatch({ type: 'increment' })}>{state.count}</button>;
}

三、万能兜底:什么时候该切换到 useReducer

判断流程:

当前用了几个 useState?
├── 1-2 个 → 继续 useState
├── 3+ 个 → 这些状态有关联吗?
│   ├── 否 → 继续 useState(或拆组件)
│   └── 是 → 更新逻辑复杂吗?
│       ├── 否 → 用对象 state + $patch 式更新
│       └── 是 → ✅ 用 useReducer

快速参考表:

场景推荐原因
单个开关 / 计数器useState简单,杀鸡不用牛刀
表单(多字段 + 验证 + 提交)useReducer状态关联,逻辑集中
步骤流 / 状态机useReducer状态转换有约束
分页 + 筛选 + 排序useReducer互相关联,一次 dispatch 原子更新
异步加载(loading + data + error)useState3 个独立状态,或用 SWR/React Query
拖拽排序(位置 + 选中 + 拖拽中)useReducer状态组合复杂
主题切换 / 语言切换useState简单独立状态

万能模板(表单 useReducer):

import { useReducer } from 'react';

// 1. 定义初始状态
const initialState = {
  values: { username: '', password: '' },
  errors: {},
  isLoading: false,
  isSubmitted: false,
};

// 2. 定义 reducer(纯函数,可单独测试)
function formReducer(state, action) {
  switch (action.type) {
    case 'SET_VALUE':
      return {
        ...state,
        values: { ...state.values, [action.field]: action.value },
      };
    case 'SET_ERROR':
      return {
        ...state,
        errors: { ...state.errors, [action.field]: action.error },
      };
    case 'START_SUBMIT':
      return { ...state, isLoading: true, errors: {} };
    case 'SUBMIT_SUCCESS':
      return { ...state, isLoading: false, isSubmitted: true };
    case 'SUBMIT_ERROR':
      return { ...state, isLoading: false, errors: action.errors };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

// 3. 组件里用
function LoginForm() {
  const [state, dispatch] = useReducer(formReducer, initialState);

  const handleChange = (field) => (e) => {
    dispatch({ type: 'SET_VALUE', field, value: e.target.value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    dispatch({ type: 'START_SUBMIT' });
    try {
      await login(state.values);
      dispatch({ type: 'SUBMIT_SUCCESS' });
    } catch (err) {
      dispatch({ type: 'SUBMIT_ERROR', errors: { form: err.message } });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={state.values.username} onChange={handleChange('username')} />
      <input type="password" value={state.values.password} onChange={handleChange('password')} />
      {state.errors.form && <span>{state.errors.form}</span>}
      <button disabled={state.isLoading}>
        {state.isLoading ? '提交中...' : '登录'}
      </button>
    </form>
  );
}

四、预防 checklist

  • 简单状态(1-2 个变量)用 useState,不要过度设计
  • 3+ 个关联状态 或 状态转换有约束 → 用 useReducer
  • 状态更新逻辑集中 在 reducer 里,组件只负责 dispatch
  • reducer 必须是纯函数:不写副作用,不调 API,只返回新 state
  • 异步操作放在组件或 action 函数里,reducer 只处理同步状态转换
  • 传 dispatch 给子组件用 Context,避免 prop drilling
  • 给 action.type 用常量(大型项目),小型项目直接字符串就行
  • reducer 的 default 分支必须返回 state,不能返回 undefined
  • useReducer 适合可测试场景:reducer 抽出来,单元测试直接调

五、一句话总结

「useState 管简单值,useReducer 管复杂状态机」。1-2 个独立状态用 useState,3+ 个关联状态或状态转换有约束就上 useReducer。reducer 是纯函数,逻辑集中、可测试、状态原子更新。别把简单问题搞复杂,也别把复杂状态硬塞进一堆 useState!

到此这篇关于React中useReducer和useState的实现示例的文章就介绍到这了,更多相关React useReducer useState内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • React代码分割的实现方法介绍

    React代码分割的实现方法介绍

    虽然一直有做react相关的优化,按需加载、dll 分离、服务端渲染,但是从来没有从路由代码分割这一块入手过,所以下面这篇文章主要给大家介绍了关于React中代码分割的方式,需要的朋友可以参考下
    2022-12-12
  • react简单实现防抖和节流

    react简单实现防抖和节流

    在日常开发中,我们经常会有防抖和节流的需要,可以减小服务器端压力,提升用户体验,本文就详细的介绍了react简单实现防抖和节流,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • React中的Hooks路由跳转问题

    React中的Hooks路由跳转问题

    这篇文章主要介绍了React中的Hooks路由跳转问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • React  memo允许你的组件在 props 没有改变的情况下跳过重新渲染的问题记录

    React  memo允许你的组件在 props 没有改变的情况下跳过重新渲染的问题记录

    使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染,这篇文章主要介绍了React  memo允许你的组件在 props 没有改变的情况下跳过重新渲染,需要的朋友可以参考下
    2024-06-06
  • React Navigation 路由传参的操作代码

    React Navigation 路由传参的操作代码

    这篇文章主要介绍了React Navigation 路由传参,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • React Mobx状态管理工具的使用

    React Mobx状态管理工具的使用

    这篇文章主要介绍了React Mobx状态管理工具的使用,MobX是一个状态管理库,它会自动收集并追踪依赖,开发人员不需要手动订阅状态,当状态变化之后MobX能够精准更新受影响的内容,另外它不要求state是可JSON序列化的,也不要求state是immutable
    2023-02-02
  • React Context 变迁及背后实现原理详解

    React Context 变迁及背后实现原理详解

    这篇文章主要为大家介绍了React Context 变迁及背后实现原理示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • React创建配置项目的实现

    React创建配置项目的实现

    本文主要介绍了React创建配置项目的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • 通过示例源码解读React首次渲染流程

    通过示例源码解读React首次渲染流程

    这篇文章主要为大家通过示例源码解读React的首次渲染流程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • React高级特性Context万字详细解读

    React高级特性Context万字详细解读

    React的context就是一个全局变量,可以从根组件跨级别在React的组件中传递。React context的API有两个版本,React16.x之前的是老版本的context,之后的是新版本的context
    2022-11-11

最新评论