使用 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 结合使用:
- 创建上下文。
- 将状态和 dispatch 函数放入上下文。
- 在树中的任意位置使用上下文。
步骤 1:创建上下文
useReducerHook 返回当前的tasks和允许你更新它们的dispatch 函数:
为了将它们传递到树中,你需要创建两个独立的上下文:
TasksContext提供当前的任务列表。TasksDispatchContext提供允许组件分发操作的函数。
将它们从单独的文件中导出,以便稍后可以从其他文件导入:
这里,你将 null 作为默认值传递给两个上下文。实际的值将由 TaskApp组件提供。
步骤 2:将状态和 dispatch 函数放入上下文
现在你可以在你的 TaskApp组件中导入这两个上下文。获取useReducer()返回的tasks 和 dispatch,并将它们提供给下方的整个树:
目前,你既通过 props 也通过上下文传递信息:
下一步,你将移除 props 的传递。
步骤 3:在树的任意位置使用 context
现在你不需要将任务列表或事件处理函数沿着树向下传递了:
相反,任何需要任务列表的组件都可以从 TasksContext中读取它:
要更新任务列表,任何组件都可以从 context 中读取dispatch 函数并调用它:
现在,TaskApp 组件不再向下传递任何事件处理函数,TaskList 组件也不再向 Task组件传递任何事件处理函数。每个组件都读取它所需的 context:
状态仍然“存活”在顶层的 TaskApp 组件中,由 useReducer管理。 但其 tasks 和 dispatch现在可以通过导入并使用这些上下文,供树中下方的每个组件使用。
将所有连接逻辑移入单个文件
你并非必须这样做,但你可以通过将 reducer 和上下文都移入单个文件来进一步简化组件。目前,TasksContext.js 仅包含两个上下文声明:
这个文件即将变得拥挤!你将把 reducer 移到同一个文件中。然后你将在同一个文件中声明一个新的TasksProvider 组件。这个组件将把所有部分整合在一起:
- 它将使用 reducer 管理状态。
- 它将向下方的组件提供两个上下文。
- 它将 接收 children 作为 prop,以便你可以向其传递 JSX。
这消除了你的 TaskApp组件中的所有复杂性和连接逻辑:
你也可以从 使用上下文的 TasksContext.js中导出函数:
当组件需要读取上下文时,可以通过这些函数来实现:
这不会以任何方式改变行为,但它让你以后可以进一步拆分这些上下文,或者在这些函数中添加一些逻辑。现在所有的上下文和 Reducer 连接逻辑都在TasksContext.js中。这使得组件保持简洁明了,专注于它们要显示的内容,而不是它们从哪里获取数据:
你可以将 TasksProvider视为屏幕的一部分,它知道如何处理任务,将useTasks视为读取任务的方式,将useTasksDispatch视为从树中下方任何组件更新任务的方式。
注意
像 useTasks 和 useTasksDispatch这样的函数被称为自定义 Hook。 如果一个函数的名字以 use开头,它就被视为一个自定义 Hook。这允许你在其中使用其他 Hook,例如useContext。
随着应用的增长,你可能会拥有许多像这样的 context-reducer 对。这是一种强大的方式来扩展你的应用,并且提升状态,以便在需要访问树深处数据时无需太多工作。
回顾
- 你可以将 reducer 与 context 结合使用,让任何组件都能读取和更新其上层的状态。
- 为了向下层组件提供状态和 dispatch 函数:
- 创建两个 context(一个用于状态,一个用于 dispatch 函数)。
- 在使用 reducer 的组件中提供这两个 context。
- 在需要读取它们的组件中使用相应的 context。
- 你可以将所有连接逻辑移到一个文件中,从而进一步简化组件。
- 你可以导出一个像
TasksProvider这样提供 context 的组件。 - 你也可以导出像
useTasks和useTasksDispatch这样的自定义 Hook 来读取它。
- 你可以导出一个像
- 在你的应用中,可以拥有许多这样的 context-reducer 组合。
