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

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

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

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


一、组件通信简单介绍

1.1 组件通信的常见方式:

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

1.2 Context 的不足:

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

  • 当Context频繁更新时,所有依赖该Context的组件都会重新渲染,即使某些组件并未使用更新后的数据,容易导致性能问题。

  • Context能解决标签的跨级传输,然而,多个Context嵌套也会导致组件层级臃肿 (比如<LoginContext.Provider>中包裹<ThemeContext.Provider>)。

  • Context本身只提供数据共享能力,它并不涉及到状态更新逻辑,需结合useState或useReducer使用,这就导致了状态管理分散问题。

因此,当应用状态逻辑变得复杂、需集中管理时,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即当触发increment、decrement、incrementByNum行为时,分别返回不同的新的状态对象。

  • 而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.js、formReducer.js),提升代码可维护性。
相关推荐
夏幻灵7 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
苍何7 小时前
开源微信流 Windows,微信聊天记录,可以直接给 Codex 和 Obsidan 了
后端
三天不学习7 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194058 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
代码什么用8 小时前
Spring基础使用
java·后端·spring
明月_清风9 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
蜗牛互联网9 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端
用户693717500138412 小时前
2026,程序员的时代拐点到了
android·前端·后端
惜鸟12 小时前
从源码看 pi 的上下文管理:原始数据永久保留,模型视角按需裁剪
后端
量化分析码农12 小时前
【Python量化策略评估体系 #05】极端行情扛得住吗?2008/2015/2020 三场景压力测试
后端