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

225 阅读5分钟

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

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

一、什么是 useReducer

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

基本语法:

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

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

二、核心组成部分详解

  1. initialState

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

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

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

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

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

    示例:

    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 如何更新状态。,结构如下:

    action = {
    	type: '意图的类型', // 
    	payload: { name: 'Alice', id: 1 } }
    }
    

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

  3. dispatch函数

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

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

    <button onClick={() => dispatch({ type: 'increment', playload: {} })}>+1</button>
    

三、 useReduceruseState的对比

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

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

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

这里以TodoList为例:

创建一个 TodoContext上下文

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

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

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

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,以便于在整个应用中使用。

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 组件,用于包裹整个应用,为应用提供上下文

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

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

TodoList组件

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组件

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组件

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;

实现效果

context-reducer.gif