v19.2Latest

使用 Reducer 和 Context 进行扩展

Reducer 可以让你整合组件的状态更新逻辑。Context 可以让你将信息深层传递给其他组件。你可以将 reducer 和 context 结合起来管理复杂屏幕的状态。

您将学习
  • 如何将 reducer 与 context 结合使用
  • 如何避免通过 props 传递状态和 dispatch 函数
  • 如何将 context 和状态逻辑保存在单独的文件中

将 reducer 与 context 结合使用

在这个来自 reducer 简介的示例中,状态由 reducer 管理。reducer 函数包含了所有的状态更新逻辑,并声明在此文件的底部:

Reducer 有助于保持事件处理程序的简短和简洁。然而,随着应用的增长,你可能会遇到另一个困难。目前,tasks 状态和 dispatch 函数仅在顶层的 TaskApp组件中可用。 为了让其他组件读取任务列表或修改它,你必须显式地 向下传递当前状态以及修改它的事件处理程序作为 props。

例如,TaskApp将任务列表和事件处理程序传递给TaskList

TaskList将事件处理程序传递给Task

在这样的小示例中,这工作得很好,但如果你中间有数十或数百个组件,向下传递所有状态和函数可能会相当令人沮丧!

这就是为什么,作为通过 props 传递的替代方案,你可能希望将tasks 状态和 dispatch函数都放入 context 中。这样,树中 TaskApp下方的任何组件都可以读取任务和派发动作,而无需重复的“prop drilling”。

以下是如何将 reducer 与 context 结合使用:

  1. 创建上下文。
  2. 状态和 dispatch 函数放入上下文。
  3. 树中的任意位置使用上下文。

步骤 1:创建上下文

useReducerHook 返回当前的tasks和允许你更新它们的dispatch 函数:

为了将它们传递到树中,你需要创建两个独立的上下文:

  • TasksContext 提供当前的任务列表。
  • TasksDispatchContext 提供允许组件分发操作的函数。

将它们从单独的文件中导出,以便稍后可以从其他文件导入:

这里,你将 null 作为默认值传递给两个上下文。实际的值将由 TaskApp组件提供。

步骤 2:将状态和 dispatch 函数放入上下文

现在你可以在你的 TaskApp组件中导入这两个上下文。获取useReducer()返回的tasksdispatch,并将它们提供给下方的整个树:

目前,你既通过 props 也通过上下文传递信息:

下一步,你将移除 props 的传递。

步骤 3:在树的任意位置使用 context

现在你不需要将任务列表或事件处理函数沿着树向下传递了:

相反,任何需要任务列表的组件都可以从 TasksContext中读取它:

要更新任务列表,任何组件都可以从 context 中读取dispatch 函数并调用它:

现在,TaskApp 组件不再向下传递任何事件处理函数,TaskList 组件也不再向 Task组件传递任何事件处理函数。每个组件都读取它所需的 context:

状态仍然“存活”在顶层的 TaskApp 组件中,由 useReducer管理。 但其 tasksdispatch现在可以通过导入并使用这些上下文,供树中下方的每个组件使用。

将所有连接逻辑移入单个文件

你并非必须这样做,但你可以通过将 reducer 和上下文都移入单个文件来进一步简化组件。目前,TasksContext.js 仅包含两个上下文声明:

这个文件即将变得拥挤!你将把 reducer 移到同一个文件中。然后你将在同一个文件中声明一个新的TasksProvider 组件。这个组件将把所有部分整合在一起:

  1. 它将使用 reducer 管理状态。
  2. 它将向下方的组件提供两个上下文。
  3. 它将 接收 children 作为 prop,以便你可以向其传递 JSX。

这消除了你的 TaskApp组件中的所有复杂性和连接逻辑:

你也可以从 使用上下文的 TasksContext.js中导出函数:

当组件需要读取上下文时,可以通过这些函数来实现:

这不会以任何方式改变行为,但它让你以后可以进一步拆分这些上下文,或者在这些函数中添加一些逻辑。现在所有的上下文和 Reducer 连接逻辑都在TasksContext.js中。这使得组件保持简洁明了,专注于它们要显示的内容,而不是它们从哪里获取数据:

你可以将 TasksProvider视为屏幕的一部分,它知道如何处理任务,将useTasks视为读取任务的方式,将useTasksDispatch视为从树中下方任何组件更新任务的方式。

注意

useTasksuseTasksDispatch这样的函数被称为自定义 Hook。 如果一个函数的名字以 use开头,它就被视为一个自定义 Hook。这允许你在其中使用其他 Hook,例如useContext

随着应用的增长,你可能会拥有许多像这样的 context-reducer 对。这是一种强大的方式来扩展你的应用,并且提升状态,以便在需要访问树深处数据时无需太多工作。

回顾

  • 你可以将 reducer 与 context 结合使用,让任何组件都能读取和更新其上层的状态。
  • 为了向下层组件提供状态和 dispatch 函数:
    1. 创建两个 context(一个用于状态,一个用于 dispatch 函数)。
    2. 在使用 reducer 的组件中提供这两个 context。
    3. 在需要读取它们的组件中使用相应的 context。
  • 你可以将所有连接逻辑移到一个文件中,从而进一步简化组件。
    • 你可以导出一个像 TasksProvider这样提供 context 的组件。
    • 你也可以导出像 useTasksuseTasksDispatch这样的自定义 Hook 来读取它。
  • 在你的应用中,可以拥有许多这样的 context-reducer 组合。