当前位置: 首页 > news >正文

【React】Hooks useReducer 详解,让状态管理更可预测、更高效

1.背景

useReducer是React提供的一个高级Hook,没有它我们也可以正常开发,但是useReducer可以使我们的代码具有更好的可读性,可维护性。

useReduceruseState 一样的都是帮我们管理组件的状态的,但是呢与useState不同的是 useReducer集中式的管理状态的。

useReducer 适用于复杂的数据类型如: 数组、对象, useState 适用于 字符串、布尔、数字 基本类型的管理。

2.用法

const [state, dispatch] = useReducer(reducer, initialArg, initfn?)

2.1 参数

  1. reducer: 是一个处理函数
    1. reducer 是一个触发state更新的纯函数,接收当前 state 和一个 action 对象,返回新的状态。
    2. reducer 必须返回新状态:直接修改原状态会导致组件不重新渲染。
  2. initialArg :是 state 的初始值。
  3. initfn :是一个可选的函数,用于初始化 state,如果编写了init函数,则默认值使用init函数的返回值,否则使用initialArg

2.2 返回值

useReducer 返回一个由两个值组成的数组:

  • 当前的 state。初次渲染时,它是 init(initialArg) 或 initialArg (如果没有 init 函数),

  • dispatch 函数。用于更新 state 并触发组件的重新渲染。

import { useReducer } from 'react';
//根据旧状态进行处理 oldState,处理完成之后返回新状态 newState
//reducer 只有被dispatch的时候才会被调用 刚进入页面的时候是不会执行的
//oldState 任然是只读的function reducer(oldState, action) {// ...return newState;
}function MyComponent() {const [state, dispatch] = useReducer(reducer, { age: 22,name:'大伟' });// ...

3.计数器案例

  • 当点击 “-” 按钮时,调用 dispatch({ type: ‘add’ }),使 count 增加。
  • 当点击 “+” 按钮时,调用 dispatch({ type: ‘sub’ }),使 count 减少。
/*** 这是一个使用 React useReducer 实现的计数器示例* 展示了如何使用 useReducer 进行复杂状态管理*/// 导入 useReducer Hook 用于状态管理
import { useReducer } from 'react';/*** 定义计数器的初始状态* count 初始值设为 -1,将通过 initFn 转换为正数*/
const initState = {count: -1,
};// 使用 TypeScript 的 typeof 操作符获取状态类型
type StateProps = typeof initState;/*** 初始化函数* @param {StateProps} param - 包含 count 属性的状态对象* @returns {StateProps} 处理后的初始状态* * 该函数在组件初始化时执行一次,用于确保初始计数为正数*/
const initFn = ({ count }: StateProps) => {return {count: Math.abs(count), // 确保计数值为正数}
};/*** Reducer 函数 - 处理状态更新逻辑* @param {StateProps} state - 当前状态* @param {Object} action - 描述如何更新状态的动作对象* @returns {StateProps} 更新后的新状态*/
const reducer = (state: StateProps, action: { type: 'ADD' | 'SUB' }) => {switch (action.type) {case 'ADD':return { count: state.count + 1 }; // 增加计数case 'SUB':return { count: state.count - 1 }; // 减少计数default:return state; // 处理未知的 action 类型}
};/*** 计数器组件* 提供增加和减少计数的功能* 使用 useReducer 实现状态管理*/
function App() {// 初始化 useReducer,获取当前状态和 dispatch 函数const [state, dispatch] = useReducer(reducer, initState, initFn)return (<div>{/* 增加按钮 */}<button onClick={() => dispatch({ type: 'ADD' })}> + </button>{/* 显示当前计数值 */}<span>{state.count}</span>{/* 减少按钮 */}<button onClick={() => dispatch({ type: 'SUB' })}> - </button></div>)
}export default App

4.购物车案例

  • App 组件使用 useReducer 来管理 data 状态,它从 initData 初始化,并通过 dispatch 分发动作来改变商品列表。
  • 商品列表通过 table 渲染,每个商品显示以下信息:
  • 物品:如果该商品的 isEdit 为 true,显示一个输入框用于修改名称;否则显示商品名称。
  • 价格:显示商品的总价(price * count)。
  • 数量:显示商品的数量,提供 - 和 + 按钮来减少或增加数量。
  • 操作:提供 编辑 按钮切换名称编辑状态,删除 按钮可以删除该商品。
  • tfoot 部分显示购物车的总价,通过 reduce 方法计算所有商品的总价。
// 引入React的useReducer钩子用于状态管理,用于处理复杂的状态逻辑
import { useReducer } from 'react';// 定义商品类型接口,描述每个商品的属性结构
interface Product {id: number;      // 商品唯一标识name: string;    // 商品名称price: number;   // 商品价格count: number;   // 商品数量isEdit: boolean; // 是否处于编辑状态
};// 定义Action类型,描述可以对商品进行的操作类型
type ActionType = {type: 'ADD' | 'SUB' | 'DEL' | 'EDIT' | 'EDIT-NAME' | 'BLUE-NAME'; // 操作类型id: number;    // 操作的商品idname?: string; // 可选的新商品名称
};// 初始商品数据数组,包含4种水果商品的信息
const initState: Product[] = [{id: 1,name: '蓝莓',price: 10,count: 1,isEdit: false,},{id: 2,name: '草莓', price: 20,count: 1,isEdit: false,},{id: 3,name: '芒果',price: 15,count: 1,isEdit: false,},{id: 4,name: '葡萄',price: 25,count: 1,isEdit: false,},
];/*** reducer函数 - 处理购物车状态更新的核心逻辑* @param state - 当前的商品状态数组* @param action - 要执行的操作对象,包含type(操作类型)、id(商品id)和可选的name(新商品名)* @returns 更新后的商品状态数组*/
const reducer = (state: Product[], action: ActionType): Product[] => {const { type, id, name } = action;switch (type) {case 'ADD': // 增加商品数量return state.map(item => item.id === id ? { ...item, count: item.count + 1 } : item);case '  ': // 减少商品数量,最小为0return state.map(item =>item.id === id ? { ...item, count: Math.max(0, item.count - 1) } : item);case 'DEL': // 删除商品return state.filter(item => item.id !== id);case 'EDIT': // 进入编辑模式return state.map(item =>item.id === id ? { ...item, isEdit: true } : item);case 'EDIT-NAME': // 更新商品名称return state.map(item =>item.id === id ? { ...item, name: name || item.name } : item);case 'BLUE-NAME': // 退出编辑模式return state.map(item =>item.id === id ? { ...item, isEdit: false } : item);default:return state;}
};// App组件:购物车的主要界面
function App() {// 使用 useReducer 管理购物车状态const [data, dispatch] = useReducer(reducer, initState);// 计算购物车商品总价const totalPrice = data.reduce((total, item) => total + item.price * item.count, 0);return (<><h1>购物车</h1>{/* 购物车商品列表表格 */}<table cellPadding={0} cellSpacing={0} border={1} width="100%"><thead><tr><th>商品名称</th><th>商品价格</th><th>商品数量</th><th>总价</th><th>操作</th></tr></thead><tbody>{/* 遍历渲染每个商品信息 */}{data.map((item) => (<tr key={item.id}><td align='center'>{/* 商品名称:可编辑状态显示输入框,否则显示文本 */}{item.isEdit ? (<inputtype="text"value={item.name}onBlur={() => dispatch({ type: 'BLUE-NAME', id: item.id })}onChange={(e) => dispatch({ type: 'EDIT-NAME', id: item.id, name: e.target.value })}/>) : item.name}</td><td align='center'>{item.price}</td><td align='center'>{/* 商品数量控制按钮 */}<button onClick={() => dispatch({ type: 'ADD', id: item.id })}> + </button>{item.count}<button onClick={() => dispatch({ type: 'SUB', id: item.id })}> - </button></td><td align='center'>{item.price * item.count}</td><td align='center'>{/* 商品操作按钮 */}<button onClick={() => dispatch({ type: 'DEL', id: item.id })}>删除</button><button onClick={() => dispatch({ type: 'EDIT', id: item.id })}>编辑</button></td></tr>))}</tbody><tfoot>{/* 显示购物车总价 */}<tr><td colSpan={4} align='right'>总价</td><td align='center'>{totalPrice}</td></tr></tfoot></table></>);
};export default App;

5. useState 的对比

特性useStateuseReducer
适用场景简单状态(如布尔值、字符串、数字)复杂状态逻辑(如多个子值、依赖前状态)
状态更新直接赋值通过 dispatch 派发动作
可测试性较低较高(Reducer 是纯函数)

6. 总结

  • 何时使用 useReducer
    • 状态更新逻辑复杂(如涉及多个子状态)。
    • 需要复用状态更新逻辑(如多个组件共享相同的逻辑)。
  • 优势:集中管理状态逻辑,便于测试和维护。

通过 useReducer,你可以更高效地管理 React 组件中的复杂状态交互。

相关文章:

  • ActiveMQ 集群搭建与高可用方案设计(一)
  • Hal库下备份寄存器
  • Spring Boot的GraalVM支持:构建低资源消耗微服务
  • 高中数学联赛模拟试题精选学数学系列第5套几何题
  • 深度学习核心架构:探明四种基础神经网络
  • STM32部分:2、环境搭建
  • Linux53 百度网盘运行(下载devtoolset11后仍提示stdc++3.0.29缺失 计划用docker容器隔离运行,计划后续再看)
  • 私人医生通过AI分析基因数据,是否有权提前告知癌症风险?
  • Fabrice Bellard(个人网站:‌bellard.org‌)介绍
  • MySQL--索引入门
  • 从零认识阿里云OSS:云原生对象存储的核心价值
  • 二极管反向恢复的定义和原理
  • JavaScript性能优化实战(8):缓存策略与离线优化
  • 基于Java的数字商品管理系统的设计与实现
  • 人工智能发展史 — 物理学诺奖之 Hopfield 联想和记忆神经网络模型
  • 前端跨域问题怎么在后端解决
  • SETNX的存在问题和redisson进行改进的原理
  • 【愚公系列】《Manus极简入门》015-时间管理顾问:“商业时间规划大师”
  • 探索 Spring AI 的 ChatClient API:构建智能对话应用的利器
  • 从实列中学习linux shell11 :在 shell 中 对于json的解析 jq 和awk 如何选择,尤其在数据清洗,数据重新组织中的应用
  • 古龙逝世四十周年|中国武侠文学学会与多所高校联合发起学术纪念活动
  • 金融监管总局:支持银行有序设立科技金融专门机构,推动研发机器人、低空飞行器等新兴领域的保险产品
  • 证监会主席吴清:我们资本市场最重要的特征是“靠谱”
  • 人民日报评论:莫让“胖东来们”陷入“棒杀”“捧杀”泥潭
  • 金沙记忆|元谋龙街渡:是起点也是终点
  • 山大齐鲁医院回应护士论文现“男性确诊子宫肌瘤”:给予该护士记过处分、降级处理