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),提升代码可维护性。
相关推荐
bobz9654 小时前
小语言模型是真正的未来
后端
你的人类朋友4 小时前
【Node&Vue】JS是编译型语言还是解释型语言?
javascript·node.js·编程语言
烛阴5 小时前
TypeScript高手密技:解密类型断言、非空断言与 `const` 断言
前端·javascript·typescript
DevYK5 小时前
企业级 Agent 开发实战(一) LangGraph 快速入门
后端·llm·agent
样子20185 小时前
Uniapp 之renderjs解决swiper+多个video卡顿问题
前端·javascript·css·uni-app·html
一只叫煤球的猫6 小时前
🕰 一个案例带你彻底搞懂延迟双删
java·后端·面试
冒泡的肥皂6 小时前
MVCC初学demo(一
数据库·后端·mysql
黑客飓风6 小时前
JavaScript 性能优化实战大纲
前端·javascript·性能优化
颜如玉6 小时前
ElasticSearch关键参数备忘
后端·elasticsearch·搜索引擎
emojiwoo7 小时前
【前端基础知识系列六】React 项目基本框架及常见文件夹作用总结(图文版)
前端·react.js·前端框架