注册
web

useReducer : hook 中的响应式状态管理


在前端开发中,状态管理是构建复杂应用的核心能力之一,而React作为主流框架,它提供了多种状态管理方案.


然而,随着应用规模扩大,组件层级加深,传统的状态传递方式似乎优点捉襟见肘了,于是,为了解决这种问题,useReduceruseContext诞生了。


今天,我将从组件通信的不足开始,逐渐深入地讲解如何通过useReducer实现高效、可维护的全局状态管理。





一、组件通信简单介绍


1.1 组件通信的常见方式:



  • 父子组件通信:通过props传递数据,子组件通过props接收父组件的数据。
  • 子父组件通信:子组件通过props传递回调函数(自定义事件)给父组件,实现数据反向传递。
  • 兄弟组件通信:通过父组件作为中间人进行传递数据。
  • 跨层级通信:使用useContext创建共享上下文(Context),直接跨层级传递状态,详细讲解可以看我之前的文章《useContext : hook中跨层级通信的优雅方案》

1.2 Context 的不足:


然而,尽管useContext解决了跨层级传递状态的问题,避免了数据臃肿,但是,它在以下场景中仍存在一些缺陷:



  • 当Context频繁更新时,所有依赖该Context的组件都会重新渲染,即使某些组件并未使用更新后的数据,容易导致性能问题
  • Context能解决标签的跨级传输,然而,多个Context嵌套也会导致组件层级臃肿(比如<LoginContext.Provider>中包裹<ThemeContext.Provider>)。
  • Context本身只提供数据共享能力,它并不涉及到状态更新逻辑,需结合useStateuseReducer使用,这就导致了状态管理分散问题。

因此,当应用状态逻辑变得复杂、需集中管理时,useReducer就成为了更优的选择。




二、useReducer详解


2.1 useReducer的定义与作用


useReducer,响应式状态管理,它是React提供的用于管理复杂状态逻辑的Hook。


useReducer通过将状态(state)交由一个纯函数(reducer)进行统一管理,并通过派发动作(dispatch action)触发状态更新,而非直接修改状态。


2.2 useReducer的参数与返回值


const [state, dispatch] = useReducer(reducer, initialState);


  • 参数1:reducer函数:根据当前状态和传入的action,返回新的状态。
  • 参数2:initialState:初始状态对象。
  • 返回值

    • state:表示当前状态值。
    • dispatch:用于触发状态更新的函数,接受一个action对象作为参数。



2.3 纯函数(Pure Function)


useReducer的参数里面,其中,要求reducer函数必须是一个纯函数


纯函数的特性:



  1. 相同输入,相同输出:给定相同的输入参数,纯函数始终返回相同的结果。
  2. 无副作用:函数内部不修改外部变量、不依赖或修改全局状态、不发起网络请求或操作DOM。
  3. 不可变更新:函数不会直接修改输入参数,而是通过创建新对象或数组返回结果。

举个例子


// 不纯的函数
let total = 0;
function addToTotal(a) {
total += a; // 修改了外部变量
return total;
}

// 纯函数
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }; // 返回新对象
default:
return state;
}
}

代码功能说明



  • addToTotal函数直接修改了外部变量total,导致结果不可预测。
  • reducer函数通过返回新对象的方式更新状态,符合纯函数的要求。



三、用计数器案例讲解useReducer


3.1 代码实现的功能


以下代码实现了一个计数器功能,它通过按钮点击+1-1修改Count值,输入自定义数值后,通过+???按钮,将该数值加到Count上。


效果如下:


mye8k-53wrb.gif
关键代码片段


import { useState,useReducer } from 'react'
import './App.css'

const initialState ={
count :0,
}
//关键代码
const reducer = (state ,action)=>{
switch(action.type){
case 'increment':
return {
count:state.count +1
};
case 'decrement':
return {
count:state.count -1
};
case 'incrementByNum':
return{
count:state.count +parseFloat(action.payload)
}
default:
return state
}
}

function App(){
const [count ,setCount] = useState(0)
const [state, dispatch]= useReducer(reducer, initialState)

return (
<>
<p>Count:{state.count}</p>
<input type="text" value={count} onChange={(e)=>setCount(e.target.value)}/>
<button onClick={()=>dispatch({type:'increment'})}> +1 </button>
<button onClick={()=>dispatch({type:'decrement'})}> -1</button>
<button onClick={()=>dispatch({type:'incrementByNum',payload:count})}> +??? </button>
</>

)
}
export default App

3.2 代码讲解:



  • 在第9行中,reducer函数通过switch语句处理三种类型的action即当触发incrementdecrementincrementByNum行为时,分别返回不同的新的状态对象。
  • dispatch函数用于触发状态更新,例如第35行,dispatch({ type: 'increment' })函数会在我们触发increment行为时,将计数器值增加1。
  • 用户可以通过输入框输入自定义数值,并通过incrementByNum操作将其加到当前计数器上。

关键部分



  1. reducer函数的设计

    • action.type决定了状态更新的逻辑,例如'increment'对应递增操作。
    • action.payload用于传递额外参数(如自定义数值)。


  2. 不可变更新

    • 所有状态更新均通过创建新对象实现(如{ count: state.count + 1 }),而非直接修改state


  3. dispatch的使用

    • dispatch接受一个action对象,触发状态更新。例如,dispatch({ type: 'incrementByNum', payload: inputValue })会将输入框中的值加到计数器上。





四、总结


4.1 useReducer的适用场景



  • 复杂状态逻辑:当状态更新逻辑涉及多个条件分支或嵌套结构时(如计数器的incrementByNum操作)。
  • 集中管理状态:通过将状态更新规则统一到reducer中,避免分散在多个组件或回调函数中。

4.2 实际应用建议



  • 结合useContext:通过useContext创建共享状态,useReducer管理状态更新,形成轻量级全局状态管理方案。
  • 模块化设计:将不同功能的reducer拆分为独立文件(如counterReducer.jsformReducer.js),提升代码可维护性。

作者:轻语呢喃
来源:juejin.cn/post/7527585340145713206

0 个评论

要回复文章请先登录注册