如何用 useReducer + useContext 构建全局状态管理

如何用 useReducer + useContext 构建全局状态管理

在 React 开发中,当应用的状态逻辑变得复杂时,仅仅依靠 props 和回调函数已经难以胜任。这时,结合 useReduceruseContext 的组合,能够为我们提供一种强大的全局状态管理方案。

一、什么是 useReducer

useReducer 是 React 提供的一个 Hook,它用于管理复杂的状态逻辑。它适合处理,多个互相关联的状态值,包含多个子值的复杂对象状态,需要集中处理的状态更新逻辑。

基本语法:

jsx 复制代码
const [state, dispatch] = useReducer(reducer, initialState);
  • state:当前状态对象。
  • dispatch:一个函数,用于发送动作(action)来触发状态更新。
  • reducer:一个纯函数,根据 action 类型返回新的状态。
  • initialState:初始状态对象。

useReducer 的本质是一种状态管理模式 ,而useState是响应式状态。设计思想可以总结为三点,action 表达"我想做什么"而不是"我要怎么改";所有的状态更新逻辑都集中在reducer中;使用无副作用的纯函数进行状态更新,确保状态变化可预测、可测试。这种方式让状态逻辑更清晰、更易于调试、也更容易复用。

二、核心组成部分详解

  1. initialState

    它是一个初始状态对象,是整个状态管理的起点,通常是一个包含多个字段的对象。这个对象代表了你的应用或组件的"数据模型"。

    jsx 复制代码
    const initialState = {
      count: 0,
      isLogin: false,
      theme: 'light',
    };
  2. reducer 函数

    在reducer中会根据动作类型处理对应业务逻辑并返回新的状态。

    reducer 是一个纯函数,接收当前状态和一个动作对象,返回一个新的状态。

    纯函数的特点:相同输入一定返回相同输出、不产生副作用(如网络请求、修改外部变量)、不直接修改原状态(必须返回新状态)

    示例:

    jsx 复制代码
    const reducer = (state, action) => {
      switch (action.type) {
        case 'increment':
          return { ...state, count: state.count + 1 };
        case 'decrement':
          return { ...state, count: state.count - 1 };
        case 'toggleTheme':
          return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
        default:
          return state;
      }
    };

    在这个函数中,我们定义了每种动作类型对应的状态更新逻辑。state参数是当前的数据状态,action` 是一个对象,用来描述"发生了什么",从而告诉 reducer 如何更新状态。,结构如下:

    js 复制代码
    action = {
    	type: '意图的类型', // 
    	payload: { name: 'Alice', id: 1 } }
    }

    其中的type必须携带,表示的是状态更新的意图。playload是可以携带的额外数据,用于修改状态。

  3. dispatch函数

    dispatch 是由 useReducer 返回的函数,用于向 reducer 发送动作。可以把它理解为:"我想要执行某个操作,请你(reducer)帮我更新状态"。

    dispatch接收的参数就是上述的action

    jsx 复制代码
    <button onClick={() => dispatch({ type: 'increment', playload: {} })}>+1</button>

三、 useReduceruseState的对比

对比维度 useState useReducer
状态类型 简单类型(number, string, boolean) 复杂对象、多个子值
更新方式 直接设置新值 通过 action 触发 reducer
可维护性 简单场景易用 复杂逻辑更清晰
可测试性 较难统一测试 reducer 是纯函数,易于测试
可扩展性 多个 useState 分散 状态集中、逻辑统一

四、结合 useContext 实现全局状态管理

当多个组件需要访问和修改同一个状态时,我们可以将 useReduceruseContext 结合起来,实现类似 Redux 的状态管理模式。

这里以TodoList为例:

创建一个 TodoContext上下文

jsx 复制代码
import { createContext } from "react";
// 创建上下文
export const TodoContext = createContext(null);

自定义一个名为useTodoContext的hook函数

这个函数的作用是用于返回TodoContext上下文。这样可以避免每次使用上下文时都需要使用useContext来获取

js 复制代码
import { useContext } from "react";
import { TodoContext } from "../TodoContext";
// 自定义 hook 返回上下文
export function useTodoContext() {
  return useContext(TodoContext);
}

自定义名为useTodos的hook函数

在这个hook中使用useReducer来管理状态 ,函数接收一个初始状态并交给useReducer来管理。hook函数通过使用useReducer返回的dispatch函数来定义不同的派遣任务函数,如addList、deleteList等。最后hook函数返回一个对象(对象中主要是数据状态,以及hook中定义的派遣函数)作上下文对象中的value,以便于在整个应用中使用。

js 复制代码
import { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "add":
      return [
        ...state,
        { id: Date.now(), container: action.payload.container, completed: false },
      ];
    case "remove":
      return state.filter((item) => item.id !== action.payload.id);
    case "changeCompleted":
      return state.map((item) => {
        if (item.id === action.payload.id) {
          return { ...item, completed: !item.completed };
        }
        return item;
      });
    default:
      return state;
  }
}
export function useTodos(inital = []) {
  // 提供参数默认值
  // value 是一个数组对象
  const [todos, dispatch] = useReducer(reducer, inital);

  const addList = (container) => {
    dispatch({
      type: "add",
      payload: {
        container,
      },
    });
  };

  const deleteList = (id) => {
    dispatch({
      type: "remove",
      payload: {
        id,
      },
    });
  };

  const changeCompleted = (id) => {
    dispatch({
      type: "changeCompleted",
      payload: {
        id,
      },
    });
  };
  return {
    todos,
    addList,
    deleteList,
    changeCompleted,
  };
}

创建 Provider 组件,用于包裹整个应用,为应用提供上下文

jsx 复制代码
import { TodoContext } from "./TodoContext"; // 引入上下文
import { useTodos } from "./hoosk/useTodos"; // 引入useTodo hook
export default function TodoProvider({ children }) {
	const todosHook = useTodos([{id: 1,container: "吃饭",completed: false,},{id: 2,container: "睡觉",completed: true,},]);    
  
  return <TodoContext.Provider value={todosHook}>{children}</StoreContext.Provider>;
}

组件结构

根组件App.jsx

jsx 复制代码
import "./App.css";
import TodoList from "./component/TodoList";
import TodoProvider from "./TodoProvider"
function App() {
  return (
    <>
      <TodoProvider>
        <TodoList />
      <TodoProvider>
    </>
  );
}
export default App;

TodoList组件

jsx 复制代码
import { useState } from "react";
import TodoForm from "./TodoForm";
import TodoItem from "./TodoItem";

function TodoList() {
  const [title, setTitle] = useState("ToDoList"); 

  return (
    <div className="continer">
      <h1 className="title">{title}</h1>
      <TodoForm />
      {/* 组件传参 */}
      <TodoItem />
    </div>
  );
}

export default TodoList;

TodoForm组件

jsx 复制代码
import { useState } from "react";
import { useTodoContext } from "../../../hoosk/useTodoContext";
function TodoForm() {
  const [container, setContainer] = useState("");
  const { addList } = useTodoContext();
  // 提交
  function handleSubmit(e) {
    e.preventDefault();
    if (container.trim()) {
      addList(container.trim());
    }
    setContainer("");
  }
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="请输入待办事项"
        value={container}
        onChange={(e) => {
          setContainer(e.target.value);
        }}
      />
      <button type="submit">添加</button>
    </form>
  );
}

export default TodoForm;

TodoItem组件

jsx 复制代码
import { useTodoContext } from "../../../hoosk/useTodoContext";
function TodoItem() {
  const { todos, deleteList, changeCompleted } = useTodoContext();
  return (
    <ul>
      {todos.map((item) => {
        // 数据驱动界面  数据变化界面会变化
        return (
          <li key={item.id} style={{ textDecoration: item.completed ? "line-through" : "none" }}>
            {item.container}
            <input type="checkbox" checked={item.completed} 
            onChange={() => changeCompleted(item.id)}
            />
            <button onClick={() => deleteList(item.id)}>删除</button>
          </li>
        );
      })}
    </ul>
  );
}
export default TodoItem;

实现效果

相关推荐
Xf3n1an1 小时前
html语法
前端·html
张拭心1 小时前
亚马逊 AI IDE Kiro “狙击”Cursor?实测心得
前端·ai编程
烛阴1 小时前
为什么你的Python项目总是混乱?层级包构建全解析
前端·python
止观止1 小时前
React响应式组件范式:从类组件到Hooks
javascript·react.js·ecmascript
@大迁世界1 小时前
React 及其生态新闻 — 2025年6月
前端·javascript·react.js·前端框架·ecmascript
红尘散仙2 小时前
Rust 终端 UI 开发新玩法:用 Ratatui Kit 轻松打造高颜值 CLI
前端·后端·rust
新酱爱学习2 小时前
前端海报生成的几种方式:从 Canvas 到 Skyline
前端·javascript·微信小程序
袁煦丞2 小时前
把纸堆变数据流!Paperless-ngx让文件管理像打游戏一样爽:cpolar内网穿透实验室第539个成功挑战
前端·程序员·远程工作
慧慧吖@3 小时前
关于两种网络攻击方式XSS和CSRF
前端·xss·csrf
徐小夕3 小时前
失业半年,写了一款多维表格编辑器pxcharts
前端·react.js·架构