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的参数与返回值

javascript 复制代码
const [state, dispatch] = useReducer(reducer, initialState);
  • 参数1:reducer函数:根据当前状态和传入的action,返回新的状态。

  • 参数2:initialState:初始状态对象。

  • 返回值

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

2.3 纯函数(Pure Function)

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

纯函数的特性:

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

举个例子

javascript 复制代码
// 不纯的函数
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上。

效果如下:

关键代码片段

javascript 复制代码
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),提升代码可维护性。
相关推荐
熟悉的新风景21 分钟前
springboot项目或其他项目使用@Test测试项目接口配置-spring-boot-starter-test
java·spring boot·后端
漠月瑾-西安2 小时前
如何在 React + TypeScript 中实现 JSON 格式化功能
javascript·jst实现json格式化
晴空月明2 小时前
分布式系统高可用性设计 - 监控与日志系统
后端
止观止2 小时前
React响应式组件范式:从类组件到Hooks
javascript·react.js·ecmascript
@大迁世界2 小时前
React 及其生态新闻 — 2025年6月
前端·javascript·react.js·前端框架·ecmascript
songroom3 小时前
【转】Rust: PhantomData,#may_dangle和Drop Check 真真假假
开发语言·后端·rust
LJianK13 小时前
Java和JavaScript的&&和||
java·javascript·python
红尘散仙3 小时前
Rust 终端 UI 开发新玩法:用 Ratatui Kit 轻松打造高颜值 CLI
前端·后端·rust
mldong3 小时前
mldong-goframe:基于 GoFrame + Vben5 的全栈快速开发框架正式开源!
vue.js·后端·go
新酱爱学习3 小时前
前端海报生成的几种方式:从 Canvas 到 Skyline
前端·javascript·微信小程序