📜 前言
在 React 开发中,状态管理是一个核心概念。useState 是最基础也是最常用的状态管理 Hook,说实话,用useState也可以很好地完成状态管理,但当应用状态变得复杂时,useReducer 往往能提供更优雅的解决方案。本文将深入探讨 useReducer 的工作原理、使用场景以及最佳实践。
❓ 什么是useReducer?
useReducer 是 React 提供的一个内置 Hook,它是一个高级Hook,它不像其它useEffect、useState等必须Hook一样,没有它我们也能完成项目的开发,但是有了它,可以写出更可维护、更可测试的 React 组件,它提供了比 useState 更结构化的状态更新方式,同时保持了 React 的简洁性。
🔍详细介绍useReducer
useState大家应该都很熟悉对吧,为了理解方便,下面我们就用与useState类比的方式来介绍useReducer吧!
首先看useState的API使用:
const [count,setCount] = useState(0);
咱们再给出useReducer的API使用:
const [state,dispatch] = useReducer(reducer,initialState)
很相似对吧!有几处不同,我们分别介绍
initialState
useReducer跟useState一样,都是一个函数,但是它的参数是reducer,initialState。
这里的reducer我们待会再说,先看看initialState:
useReducer里的initialState和useState里面的0是一样的,都是初始值
只不过,我们说useReducer适用于复杂的项目的状态管理,这里的initialState往往值得就不是0,1,2,3或者普通的字符串这些简单的初始值了,往往我们在initialState里面会放一些对象等,比如
const initialState = {
count:0,
isLogin:false,
theme:'light'
}
const [state,dispatch] = useReducer(reducer,initialState)
这是我们平常在使用useReducer管理的一个案例,你可以看到initialState是一个对象,会放一些重要的内容供它管理。
当然了,这里只是常见的业务场景,事实上,不论是
initialState还是useState的初始值,我们放对象和普通值都是可以的!
reducer
接下来讲解另一个参数reducer,它是一个函数,负责处理状态更新的逻辑,它的结构如下:
reducer 函数的结构
一个典型的 reducer 函数通常包含以下部分:
function reducer(state, action) {
switch (action.type) {
case 'ACTION_TYPE_1':
// 处理第一种动作
return { ...state, /* 更新部分状态 */ };
case 'ACTION_TYPE_2':
// 处理第二种动作
return { ...state, /* 更新部分状态 */ };
default:
// 如果 action 不匹配任何 case,返回原状态
return state;
}
}
你可以看到:通常它的结构中,会有switch结构,对于不同的动作,它会进行限制,举个例子:
const reducer = (state,action) => {
switch(action.type) {
case 'increment':
return {
count: state.count + 1
}
case 'decrement':
return {
count: state.count-1
}
default:
return state;
}
}
对于相加和相减操作increment和decrement,它会进行代理,假如某个button想操作状态state,在传统的useState中,它可能就直接setState了,但是在useReduer中,正是有const [state,dispatch] = useReducer(reducer,initialState) 里面的reducer函数存在,它就不会那么轻易直接setState,而是根据不同的操作(相加or相减)来限制住
特别是在大型项目开发中,对于用户金额这种敏感数据,可不能直接setState等,万一程序员哪天开发的时候头脑昏昏的,一不小心设置成了啥数字,可是容易挨骂的,而用useReduer管理这种敏感数据,由于reduer函数的存在,使得我们可以更放心地管理敏感数据!!!
所以reducer函数在设计时,需要是一个纯函数!
纯函数:reducer 不应该有副作用(如 API 调用、修改外部变量等),相同的输入应该总是产生相同的输出。
核心特征
-
相同输入总是返回相同输出
- 只要输入参数相同,无论调用多少次、何时调用,输出结果必定相同
-
无副作用
- 不会改变任何外部状态(不修改传入的参数、全局变量等)
- 不会产生外部可观察的变化(如网络请求、IO操作等)
dispatch
现在,正式来介绍:const [state,dispatch] = useReducer(reducer,initialState) 里面的dispatch
大家知道const [state,setState] = useState("0")里面的setState是用来更改响应式状态state的对吧
dispatch和它的位置一样,它们的功能也是相似的,dispatch是用来触发状态更改的,只不过,状态真正的更改是在reducer,它不能像setState一样直接更改,它会被reducer给限制住。
dispatch 函数:
- 接收一个 action 对象 作为参数
- 将当前状态和这个 action 传递给
reducer函数 - 用 reducer 返回的新状态更新组件
看一个例子你就明白了:
import {
useState,
useReducer
} from 'react'
import './App.css'
//reducer拿到的action就是来自于dispatch的
const reducer = (state,action) => {
switch(action.type) {
case 'increment':
return {
count: state.count + 1
}
case 'decrement':
return {
count: state.count-1
}
default:
return state;
}
}
const initialState = {
count:0,
}
function App(){
const [count,setCount] = useState(0);
const [state,dispatch] = useReducer(reducer,initialState)
return (
<>
<p>Count:{state.count}</p>
//dispatch 接受一个{type:事件类型}为参数
<button onClick={() => dispatch({type:'increment'})}>+</button>
<button onClick={() => dispatch({type:'decrement'})}>-</button>
</>
)
}
export default App
在这个数字修改案例中,dispatch和reducer就像魂斗罗一样,紧密配合
我们点击button按钮尝试增加or减少数字count,这个时候触发的action传递给dispatch,然后dispatch再将这个传递给reducer,触发真正的更改逻辑,reducer通过switch检测到,增加或减少,替代完成修改
同样,我们还可以触发reducer的同时携带数据:比如
const [state,dispatch] = useReducer(reducer,initialState)
const [count,setCount] = useState("");
return
(
省略重复内容...
//触发事件时携带数据
<input type="text" value={count} onChange={(e) => setCount(e.target.value)}/>
// payload会放在action发送给reduer
<button onClick={() => dispatch({type:'incrementByNum',payload:count})}>
incrementByNum
</button>
)
const reducer = (state,action) => {
switch(action.type) {
省略重复内容...
//当携带数据payload时
case 'incrementByNum':
return {
count: state.count + parseInt(action.payload)
}
}
}
所以我们总结一下dispatch的核心特性:
-
触发状态更新:
dispatch({ type: 'increment' }); -
不可变性保证:
- 不会直接修改当前 state
- 总是通过 reducer 返回新状态
-
稳定引用:
- 在组件生命周期内,dispatch 函数的引用保持不变
- 可以安全地放入依赖数组或传递给子组件
🌟重要应用
上面介绍了useReduer的API知道了useReduer的API const [state,dispatch] = useReducer(reducer,initialState),当你知道API中不同部分的意义和使用后,现在,对于useReduer你也能够很清楚它可以保护一些重要的数据,通过限制它们的修改操作来实现
下面再总结一下useReduer的几个重要应用:
1. 管理复杂表单状态(比 useState 更清晰)
// 传统方式:多个 useState
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
// useReducer 方式:统一管理
const formReducer = (state, action) => {
switch(action.type) {
case 'UPDATE_FIELD':
return { ...state, [action.field]: action.value }
case 'SUBMIT':
return { ...state, isSubmitting: true }
// ...
}
}
✓ 好处:所有表单状态在一个对象里,更新逻辑集中管理
2. 实现撤销/重做功能(超实用!)
const historyReducer = (state, action) => {
const { past, present, future } = state
switch(action.type) {
case 'UNDO':
return {
past: past.slice(0, -1),
present: past[past.length - 1],
future: [present, ...future]
}
// ...
}
}
✓ 好处:轻松记录状态历史,实现时间旅行效果
3. 处理异步操作(如API请求)
const fetchReducer = (state, action) => {
switch(action.type) {
case 'FETCH_START':
return { ...state, loading: true }
case 'FETCH_SUCCESS':
return { data: action.payload, loading: false, error: null }
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.error }
}
}
// 使用:清晰表达请求的不同阶段
dispatch({ type: 'FETCH_START' })
try {
const data = await fetchData()
dispatch({ type: 'FETCH_SUCCESS', payload: data })
} catch(error) {
dispatch({ type: 'FETCH_ERROR', error })
}
✓ 好处:让异步状态变化更有条理,避免混乱的setState
这些应用场景展示了useReducer如何让你的React代码更:
- 有条理(逻辑集中管理)
- 可维护(更新规则一目了然)
- 可扩展(轻松添加新功能)
✅总结
useReducer就像一个公司会计一样,时刻管理着数据状态,当我们要增加数据状态时,需要向这个公司会计发送action请求,它接收到,同意了之后才能按规定增加,减少和修改也是一样...
这篇文章像是一篇useReducer的指南,通过这篇文章你能够了解其的概念和大致使用方法与场景,但是useReducer是一个相对复杂和困难的hook,如果想要了解更多它的深入知识,还是建议去阅读react官方文档进行深入了解哦!