【React】Hooks useReducer 详解,让状态管理更可预测、更高效
1.背景
useReducer
是React提供的一个高级Hook,没有它我们也可以正常开发,但是useReducer
可以使我们的代码具有更好的可读性,可维护性。
useReducer
跟 useState
一样的都是帮我们管理组件的状态
的,但是呢与useState
不同的是 useReducer
是集中式
的管理状态的。
useReducer
适用于复杂的数据类型如: 数组、对象, useState
适用于 字符串、布尔、数字 基本类型的管理。
2.用法
const [state, dispatch] = useReducer(reducer, initialArg, initfn?)
2.1 参数
reducer
: 是一个处理函数reducer
是一个触发state更新的纯函数,接收当前state
和一个action
对象,返回新的状态。reducer
必须返回新状态:直接修改原状态会导致组件不重新渲染。
initialArg
:是state
的初始值。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
的对比
特性 | useState | useReducer |
---|---|---|
适用场景 | 简单状态(如布尔值、字符串、数字) | 复杂状态逻辑(如多个子值、依赖前状态) |
状态更新 | 直接赋值 | 通过 dispatch 派发动作 |
可测试性 | 较低 | 较高(Reducer 是纯函数) |
6. 总结
- 何时使用
useReducer
- 状态更新逻辑复杂(如涉及多个子状态)。
- 需要复用状态更新逻辑(如多个组件共享相同的逻辑)。
- 优势:集中管理状态逻辑,便于测试和维护。
通过 useReducer
,你可以更高效地管理 React 组件中的复杂状态交互。
相关文章:
【React】Hooks useReducer 详解,让状态管理更可预测、更高效
1.背景 useReducer是React提供的一个高级Hook,没有它我们也可以正常开发,但是useReducer可以使我们的代码具有更好的可读性,可维护性。 useReducer 跟 useState 一样的都是帮我们管理组件的状态的,但是呢与useState不同的是 useReducer 是集…...

日志之ClickHouse部署及替换ELK中的Elasticsearch
文章目录 1 ELK替换1.1 Elasticsearch vs ClickHouse1.2 环境部署1.2.1 zookeeper 集群部署1.2.2 Kafka 集群部署1.2.3 FileBeat 部署1.2.4 clickhouse 部署1.2.4.1 准备步骤1.2.4.2 添加官方存储库1.2.4.3 部署&启动&连接1.2.4.5 基本配置服务1.2.4.6 测试创建数据库和…...
亚远景-ASPICE vs ISO 21434:汽车软件开发标准的深度对比
ASPICE(Automotive SPICE)和ISO 21434是汽车软件开发领域的两大核心标准,分别聚焦于过程质量与网络安全。以下从核心目标、覆盖范围、实施重点、协同关系及行业价值五个维度进行深度对比分析: 一、核心目标对比 ASPICE࿱…...
51单片机快速成长路径
作为在嵌入式领域深耕18年的工程师,分享一条经过工业验证的51单片机快速成长路径,全程干货无注水: 一、突破认知误区(新手必看) 不要纠结于「汇编还是C」:现代开发90%场景用C,掌握指针和内存管…...
使用 NGINX 实现 HTTP Basic 认证ngx_http_auth_basic_module 模块
一、前言 在 Web 应用中,对部分资源进行访问控制是十分常见的需求。除了基于 IP 限制、JWT 验证、子请求校验等方式外,最经典也最简单的一种方式便是 HTTP Basic Authentication。NGINX 提供的 ngx_http_auth_basic_module 模块支持基于用户名和密码的基…...

解构与重构:自动化测试框架的进阶认知之旅
目录 一、自动化测试的介绍 (一)自动化测试的起源与发展 (二)自动化测试的定义与目标 (三)自动化测试的适用场景 二、什么是自动化测试框架 (一)自动化测试框架的定义 &#x…...

DockerDesktop替换方案
背景 由于DockerDesktop并非开源软件,如果在公司使用,可能就有一些限制,那是不是除了使用DockerDesktop外,就没其它办法了呢,现在咱们来说说替换方案。 WSL WSL是什么,可自行百度,这里引用WS…...

力扣热题100之搜索二维矩阵 II
题目 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 代码 方法一:直接全体遍历 这个方法很直接,但是居然没有超时,…...

docker操作镜像-以mysql为例
Docker安装使用-CSDN博客 docker操作镜像-以mysql为例 当安装一个新的镜像时可以登录https://hub.docker.com/直接搜索想要安装的镜像,查看文档 1)拉取镜像 docker pull mysql 或者 docker pull mysql:版本号 然后直接跳到第4)步即可 2…...

使用OpenCV 和 Dlib 进行卷积神经网络人脸检测
文章目录 引言1.准备工作2.代码解析2.1 导入必要的库2.2 加载CNN人脸检测模型2.3 加载并预处理图像2.4 进行人脸检测2.5 绘制检测结果2.6 显示结果 3.完整代码4.性能考虑5.总结 引言 人脸检测是计算机视觉中最基础也最重要的任务之一。今天我将分享如何使用dlib库中的CNN人脸检…...

React 实现 JWT 登录验证的最小可运行示例
下面是一个用 React 实现 JWT 登录验证的最小可运行示例,包含: React 前端:登录、保存 Token、获取用户数据。模拟后端:用 mock API(你也可以接真后端)。 🧱 技术栈 React(使用 Vi…...

Power Query精通指南1:查询结构设计、数据类型、数据导入与迁移(平面文件、Excel、Web)
文章目录 零、Power Query简介0.1 Power Query 主要功能0.2 Power Query 的优势0.3 Power Query 组件 一、Power Query数据处理基本流程1.1 前期准备1.2 提取1.3 转换1.3.1 Power Query 编辑器界面1.3.2 默认转换1.3.3 自定义转换 1.4 加载1.4.1 自动检测数据类型1.4.2 重命名查…...

vue2开发者sass预处理注意
vue2开发者sass预处理注意 sass的预处理器,早年使用node-sass,也就是vue2最初默认的编译器。 sass官方推出了dart-sass来替代。 node-sass已经停维很久了。 vue3默认使用的是dart-sass。 Uniapp的官方文档截图 从 HBuilderX 4.56 ,vue2 …...
淘宝按图搜索商品(拍立淘)Java 爬虫实战指南
在电商领域,按图搜索商品功能为用户提供了更直观、便捷的购物体验。淘宝的拍立淘功能更是凭借其强大的图像识别技术,成为许多开发者和商家关注的焦点。本文将详细介绍如何利用 Java 爬虫技术实现淘宝按图搜索商品功能,包括注册账号、上传图片…...
安卓基础(封装引用)
情况 1:普通 Java 项目(非 Android) src/ ├── com/ │ ├── example/ │ │ ├── utils/ │ │ │ └── A.java // 工具类 A │ │ └── main/ │ │ └── B.java // 主类 B A…...
深入理解 Docker 网络原理:构建高效、灵活的容器网络
在现代软件开发中,Docker 已经成为了容器化技术的代名词,广泛应用于开发、测试和生产环境。Docker 使得开发者能够将应用及其依赖打包成一个轻量级的容器,并通过 Docker 容器化技术来实现高效的部署与管理。 然而,在日常使用 Dock…...

使用 Selenium 爬取动态网页数据 —— 实战与坑点详解
本文记录了笔者在爬取网页数据过程中遇到的各种技术挑战,包括页面动态渲染、JavaScript 注入等问题,并最终给出一个可运行的完整方案。 文章目录 网页获取不到数据🚀 尝试用 Selenium 渲染页面 网页获取不到数据 某网页数据依赖大量 JavaSc…...
React 笔记[1] hello world
React 笔记[1] hello world 明白了!既然你已经安装了 Node.js,我们可以 从零开始搭建一个 React Tailwind CSS 的 Hello World 项目。我将一步步列出操作指令,你只需要在终端里依次执行。 ✅ 第一步:初始化项目 mkdir my-hello…...
Verilog Test Fixture 时钟激励
1、占空比50%时钟产生 always begin<clock> 1b0 ;#<PERIOD/2> ;<clock> 1b1 ;#<PERIOD/2> ; end reg <clock> 1b0 ;alwaysbegin#<PERIOD/2> ;<clock> ~<clock> ;end 2…...

守护数字家园:个人博客安全防护指南
前言 在之前的文章《WordPress个人博客搭建(一)》《WordPress个人博客搭建(二)》《WordPress个人博客搭建(三)》中,我们已经在非凡云云服务器上,借助1Panel搭建起属于自己的数字庭院…...

【网络编程】三、TCP网络套接字编程
文章目录 TCP通信流程Ⅰ. 服务器日志类实现Ⅱ. TCP服务端1、服务器创建流程2、创建套接字 -- socket3、绑定服务器 -- bind🎏4、服务器监听 -- listen🎏5、获取客户端连接请求 -- acceptaccept函数返回的套接字描述符是什么,不是已经有一个了…...

trae ai编程工具
Trae,致力于成为真正的 AI 工程师(The Real Al Engineer)。Trae 旗下的 AI IDE 产品,以智能生产力为核心,无缝融入你的开发流程,与你默契配合,更高质量、高效率完成每一个任务。 版本差异 国内…...
STM32系统定时器以及微秒延时函数分析
在CubeMX生成的工程中系统时钟节拍配置的函数为: __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) {HAL_StatusTypeDef status HAL_OK;/* Check uwTickFreq for MisraC 2012 (even if uwTickFreq is a enum type that doesnt take the value zero)…...

神经网络发展历程——积跬步至千里
神经网络类型层线性or非线性创新问题备注感知器单层线性模型,输出 1 1 1, − 1 -1 −1误差反馈学习阈值函数不可导,构造学习规则与感知器准则等价线性神经元单层线性模型梯度下降法训练参数线性函数,多层仍是线性变换本质上是最小…...
Java 24:重构数字信任边界 —— 后量子时代的智能安全防御体系构建
引言 在量子计算阴影与 AI 驱动攻击交织的网络安全新纪元,Java 平台正经历着自诞生以来最深刻的安全架构革新。作为企业级应用的核心基础设施,Java 24 不仅延续了 “一次编写,处处运行” 的跨平台基因,更以后量子密码学引擎、动态…...

荣耀A8互动娱乐组件部署实录(第2部分:界面逻辑与资源加载机制)
作者:从 Spine 骨骼动画里抠图三小时没睡的美术兼前端苦工 一、界面整体架构拆解 荣耀A8组件采用的是典型的分模块 UI 架构,即:主界面为入口容器,不同子页面(如商城、银行、客服、游戏入口)以逻辑功能划分…...

mac 使用 Docker 安装向量数据库Milvus独立版的保姆级别教程
Milvus 特点:开源的云原生向量数据库,支持多种索引类型和GPU加速,能够在亿级向量规模下实现低延迟高吞吐。具有灵活的部署选项和强大的社区支持。 适用场景:适合处理超大规模数据和高性能需求的应用,如图像搜索、推荐…...

技术视界 | 青龙机器人训练地形详解(一):如何创建一个地形
机器人强化学习中的地形训练是利用强化学习算法让机器人在不同地形环境中通过试错学习最优行为策略的过程,通过环境建模、策略学习与优化等环节,使机器人能够自主适应复杂多变的地形,提高其移动效率、稳定性和自主性,减少人为干预…...
网络安全系列--《文章1:网络安全基础与核心概念》
课程1:网络安全基础与核心概念 学习内容 1. 网络安全定义 网络安全是通过技术、管理及法律手段保护网络系统的硬件、软件及数据,使其免受破坏、篡改或泄露,确保系统稳定运行并提供可靠服务。其核心目标包括保密性、完整性、可用性、可控性及…...

2025-05-04 Unity 网络基础6——TCP心跳消息
文章目录 1 Disconnect 方法2 心跳消息 在客户端主动退出时,我们会调用 socket 的 ShutDown() 和 Close() 方法,但调用这两个方法后,服务器端无法得知客户端已经主动断开。 本文主要介绍在网络通信中,如何服务端如何判断客…...