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 并触发组件的重新渲染。
tsx
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 减少。
tsx
/**
* 这是一个使用 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 方法计算所有商品的总价。
tsx
// 引入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; // 操作的商品id
name?: 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 ' ': // 减少商品数量,最小为0
return 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 ? (
<input
type="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 组件中的复杂状态交互。