0%

已完成 0/29 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 26 关 含代码 约 14 分钟

结合 Reducer 与 Context:构建清晰可扩展的全局数据层

结合 Reducer 与 Context:构建清晰可扩展的全局数据层

在前面的两关中,你分别掌握了两个强大的工具:

  • Reducer:让你把复杂的组件状态更新逻辑收拢到一个独立的纯函数中;
  • Context:让你能够跨越层层嵌套,直接把数据送到深层组件手中。

但是,如果你只单独使用它们:

  • 单独使用 Reducer 时,你依然需要把 tasks 和 dispatch 作为 props 逐级传给深层的孙子组件;
  • 单独使用 Context 时,如果要修改深层数据,你不得不传递许多分散的 setter 函数。

如果把它们强强联合呢? Reducer 负责管理复杂业务逻辑,Context 负责将状态与分发能力全域广播! 这正是现代大型 React 应用最为核心、最为优雅的经典架构!


1. 架构设计:为什么要把 State 和 Dispatch 分开放入两个 Context?

我们推荐创建两个平行的 Context:

  1. TasksContext:专门用来向下广播任务列表数据(供需要展示任务的组件读取);
  2. TasksDispatchContext:专门用来向下广播 dispatch 函数(供需要新增、删除任务的按钮读取)。

为什么不把它们打包成一个 value={{ tasks, dispatch }}?

这是一个极其精妙的性能与设计考量:

  • 很多组件(比如页面底部的“清空已完成”按钮)只需要派发动作,根本不需要关心任务列表具体有几项;
  • 如果把它们捆绑在一个 Context 中,每次任务列表一发生改变,那些原本只关心 dispatch 的组件也会被强迫一起重新渲染!
  • 读写分离能让组件只订阅自己真正关心的那一部分,带来极其出色的性能表现和代码纯度。

2. 封装独立的 Provider 模块

为了让应用结构极其清晰,我们通常新建一个名为 TasksContext.js 的独立文件,把 Context、Reducer 和 Provider 全部内聚封装在里面:

// TasksContext.js
import { createContext, useContext, useReducer } from 'react';

// 1. 创建两个独立的 Context
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);

// 2. 核心 Provider 组件
export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

// 3. 导出两个超方便的自定义 Hook!
export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

// 4. Reducer 逻辑纯函数
function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, { id: action.id, text: action.text, done: false }];
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    case 'changed': {
      return tasks.map(t => t.id === action.task.id ? action.task : t);
    }
    default: {
      throw Error('未知的 action: ' + action.type);
    }
  }
}

const initialTasks = [
  { id: 0, text: '学习 React 官方基础', done: true },
  { id: 1, text: '掌握 Reducer 与 Context', done: true },
  { id: 2, text: '独立构建完整应用', done: false }
];

3. 在外层引入 Provider,底层享受极简代码

现在,打开你的应用入口 App.js:

// App.js
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function App() {
  return (
    <TasksProvider>
      <h1>每日研习清单</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

看看 App.js 变得多么不可思议的干净!没有一个状态变量,没有一行事件处理函数,没有一层层往下传的 prop!

底层组件如何消费?

在需要添加任务的 AddTask 组件中,直接调用我们导出的自定义 Hook:

// AddTask.js
import { useState } from 'react';
import { useTasksDispatch } from './TasksContext.js';

let nextId = 3;

export default function AddTask() {
  const [text, setText] = useState('');
  const dispatch = useTasksDispatch(); // 瞬间获得分发能力!

  return (
    <>
      <input
        placeholder="添加新任务"
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => {
        setText('');
        dispatch({
          type: 'added',
          id: nextId++,
          text: text
        });
      }}>
        添加
      </button>
    </>
  );
}

在需要展示列表的 TaskList 中:

// TaskList.js
import { useTasks } from './TasksContext.js';

export default function TaskList() {
  const tasks = useTasks(); // 瞬间拿到全域数据!

  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>{task.text}</li>
      ))}
    </ul>
  );
}

整个应用的数据层和表现层实现了完美的解耦!任何深层组件想要读数据就用 useTasks(),想要改数据就用 useTasksDispatch()。


总结

  • 结合 Reducer(业务逻辑处理)与 Context(全域数据分发)是中大型应用的最佳实践;
  • 将 State 与 Dispatch 分别存放在两个 Context 中,实现读写分离并优化性能;
  • 封装自定义 Provider(如 TasksProvider)把数据管理逻辑集中在一个模块中;
  • 导出自定义 Hook(如 useTasks)让消费组件的使用体验达到极致的简洁。
下一站:使用 Ref 引用值:在不重新渲染的情况下记住信息 →
© 2026 React 官方教程