react之Reducer和Context的联合使用

第三章 - 状态管理

使用 Reducer 和 Context 拓展你的应用

Reducer 可以整合组件的状态更新逻辑。Context 可以将信息深入传递给其他组件。你可以组合使用它们来共同管理一个复杂页面的状态。

结合使用 reducer 和 context

在 reducer 介绍 的例子里面,状态被 reducer 所管理。reducer 函数包含了所有的状态更新逻辑并在此文件的底部声明:

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';export default function TaskApp() {const [tasks, dispatch] = useReducer(tasksReducer,initialTasks);function handleAddTask(text) {dispatch({type: 'added',id: nextId++,text: text,});}function handleChangeTask(task) {dispatch({type: 'changed',task: task});}function handleDeleteTask(taskId) {dispatch({type: 'deleted',id: taskId});}return (<><h1>Day off in Kyoto</h1><AddTaskonAddTask={handleAddTask}/><TaskListtasks={tasks}onChangeTask={handleChangeTask}onDeleteTask={handleDeleteTask}/></>);
}function tasksReducer(tasks, action) {switch (action.type) {case 'added': {return [...tasks, {id: action.id,text: action.text,done: false}];}case 'changed': {return tasks.map(t => {if (t.id === action.task.id) {return action.task;} else {return t;}});}case 'deleted': {return tasks.filter(t => t.id !== action.id);}default: {throw Error('Unknown action: ' + action.type);}}
}let nextId = 3;
const initialTasks = [{ id: 0, text: 'Philosopher’s Path', done: true },{ id: 1, text: 'Visit the temple', done: false },{ id: 2, text: 'Drink matcha', done: false }
];

Reducer 有助于保持事件处理程序的简短明了。但随着应用规模越来越庞大,你就可能会遇到别的困难。目前,tasks 状态和 dispatch 函数仅在顶级 TaskApp 组件中可用。要让其他组件读取任务列表或更改它,你必须显式 传递 当前状态和事件处理程序,将其作为 props。

例如,TaskApp 将 一系列 task 和事件处理程序传递给 TaskList

<TaskListtasks={tasks}onChangeTask={handleChangeTask}onDeleteTask={handleDeleteTask}
/>

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

<Tasktask={task}onChange={onChangeTask}onDelete={onDeleteTask}
/>

在像这样的小示例里这样做没什么问题,但是如果你有成千上百个组件,传递所有状态和函数可能会非常麻烦!

这就是为什么,比起通过props传递它们,你可能想把tasks状态和dispatch函数都放入context。这样,所有的TaskApp组件树之下的组件都不必一直往下传props而可以直接读取tasks 和 dispatch 函数。

下面将介绍如何结合使用 reducer 和 context:

  1. 创建context
  2. 将state 和 dispatch 放入 context
  3. 在组件树的任何地方使用context

第一步:创建context

useReducer 返回当前的 tasksdispatch 函数来让你更新它们:

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

为了将他们从组件树往下传,你将创建两个不同的context:

  • TasksContext 提供当前的 tasks 列表。
  • TasksDispatchContext 提供了一个函数可以让组件分发动作。

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

import { createContext } from 'react';export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

在这里,你把 null 作为默认值传递给两个 context。实际值是由 TaskApp 组件提供的。

第二步:将state 和 dispatch函数 放入 context

现在,你可以将所有的context导入 TaskApp 组件。获取 useReducer() 返回 tasks 和 dispatch 并将它们提供给整个组件树。

import { TasksContext, TasksDispatchContext } from './TasksContext.js';export default function TaskApp() {const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);// ...return (<TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}>...</TasksDispatchContext.Provider></TasksContext.Provider>);
}

第三步:在组件树中的任何地方使用context

现在你不需要将tasks和事件处理程序在组件树中传递:

<TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}><h1>Day off in Kyoto</h1><AddTask /><TaskList /></TasksDispatchContext.Provider>
</TasksContext.Provider>

相反,任何需要tasks的组件都可以从TaskContext中读取它:

export default function TaskList() {const tasks = useContext(TasksContext)
}

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

export default function AddTask() {const [text, setText] = useState('');const dispatch = useContext(TasksDispatchContext);// ...return (// ...<button onClick={() => {setText('');dispatch({type: 'added',id: nextId++,text: text,});}}>Add</button>// ...

TaskApp 组件不会向下传递任何事件处理程序,TaskList 也不会。每个组件都会读取它需要的 context:

import { useState, useContext } from 'react';
import { TasksContext, TasksDispatchContext } from './TasksContext.js';export default function TaskList() {const tasks = useContext(TasksContext);return (<ul>{tasks.map(task => (<li key={task.id}><Task task={task} /></li>))}</ul>);
}function Task({ task }) {const [isEditing, setIsEditing] = useState(false);const dispatch = useContext(TasksDispatchContext);let taskContent;if (isEditing) {taskContent = (<><inputvalue={task.text}onChange={e => {dispatch({type: 'changed',task: {...task,text: e.target.value}});}} /><button onClick={() => setIsEditing(false)}>Save</button></>);} else {taskContent = (<>{task.text}<button onClick={() => setIsEditing(true)}>Edit</button></>);}return (<label><inputtype="checkbox"checked={task.done}onChange={e => {dispatch({type: 'changed',task: {...task,done: e.target.checked}});}}/>{taskContent}<button onClick={() => {dispatch({type: 'deleted',id: task.id});}}>Delete</button></label>);
}

state 仍然 “存在于” 顶层 Task 组件中,由 useReducer 进行管理。不过,组件树里的组件只要导入这些 context 之后就可以获取 tasksdispatch

将相关逻辑迁移到一个文件当中

这不是必须的,但你可以通过将reducer 和 context 移动到单个文件中来进一步整理组件。目前,"TasksContext.js"仅包含两个context声明:

import { createContext } from 'react'export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

来给这个文件添加更多代码!将 reducer 移动到此文件中,然后声明一个新的 TasksProvider 组件。此组件将所有部分连接在一起:

  1. 它将管理 reducer 的状态。
  2. 它将提供现有的 context 给组件树。
  3. 它将 把 children 作为 prop,所以你可以传递 JSX。
export function TasksProvider({ children }) {const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);return (<TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}>{children}</TasksDispatchContext.Provider></TasksContext.Provider>);
}

这将使 TaskApp 组件更加直观:

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';export default function TaskApp() {return (<TasksProvider><h1>Day off in Kyoto</h1><AddTask /><TaskList /></TasksProvider>);
}

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

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

组件可以通过以下函数读取 context:

const tasks = useTasks();const dispatch = useTasksDispatch();

这不会改变任何行为,但它会允许你之后进一步分割这些 context 或向这些函数添加一些逻辑。现在所有的 context 和 reducer 连接部分都在 TasksContext.js 中。这保持了组件的干净和整洁,让我们专注于它们显示的内容,而不是它们从哪里获得数据:

import { useState } from 'react';
import { useTasks, useTasksDispatch } from './TasksContext.js';export default function TaskList() {const tasks = useTasks();return (<ul>{tasks.map(task => (<li key={task.id}><Task task={task} /></li>))}</ul>);
}function Task({ task }) {const [isEditing, setIsEditing] = useState(false);const dispatch = useTasksDispatch();let taskContent;if (isEditing) {taskContent = (<><inputvalue={task.text}onChange={e => {dispatch({type: 'changed',task: {...task,text: e.target.value}});}} /><button onClick={() => setIsEditing(false)}>Save</button></>);} else {taskContent = (<>{task.text}<button onClick={() => setIsEditing(true)}>Edit</button></>);}return (<label><inputtype="checkbox"checked={task.done}onChange={e => {dispatch({type: 'changed',task: {...task,done: e.target.checked}});}}/>{taskContent}<button onClick={() => {dispatch({type: 'deleted',id: task.id});}}>Delete</button></label>);
}

你可以将 TasksProvider 视为页面的一部分,它知道如何处理 tasks。useTasks 用来读取它们,useTasksDispatch 用来从组件树下的任何组件更新它们。

随着应用的增长,你可能会有许多这样的 context 和 reducer 的组合。这是一种强大的拓展应用并 提升状态 的方式,让你在组件树深处访问数据时无需进行太多工作。

摘要
  • 你可以将reducer和context结合,让任何组件读取和更新它的状态
  • 为子组件提供state 和 dispatch函数:
    1. 创建两个context(一个用于state,一个用于dispatch函数)
    2. 让组件的context使用reducer
    3. 使用组件中需要读取的context
  • 你可以将所有传递信息的代码移动到单个文件来进一步整理组件
    • 你可以导出一个像 TasksProvider 可以提供 context 的组件。
    • 你也可以导出像 useTasksuseTasksDispatch 这样的自定义 Hook。
  • 你可以在你的应用程序中大量使用context 和 reducer 的组合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/836566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何修复Windows中的“无Internet,安全”错误?这里有详细步骤

序言 在Windows计算机上连接到互联网非常容易,但是,当你尝试连接到网络时,Windows有时可能会显示消息“无Internet,安全”。此消息的确切含义是什么?如何修复?以下是你需要了解的所有信息。 为什么Windows显示“无Internet,安全”消息 “无Internet,安全”消息是一个…

简约在线生成短网址系统源码 短链防红域名系统 带后台

简约在线生成短网址系统源码 短链防红域名系统 带后台 安装教程&#xff1a;访问 http://你的域名/install 进行安装 源码免费下载地址抄笔记 (chaobiji.cn)https://chaobiji.cn/

图像分割各种算子算法-可直接使用(Canny、Roberts、Sobel)

Canny算子&#xff1a; import numpy as np import cv2 as cv from matplotlib import pyplot as pltimg cv.imread("../test_1_1.png") edges cv.Canny(img, 100, 200)plt.subplot(121),plt.imshow(img,cmap gray) plt.title(Original Image), plt.xticks([]), …

MySQL数据库之UNION 和JOIN连接的区别?

UNION和JOIN连接是用于合并表中数据的两种不同方法。 1、JOIN连接&#xff1a; 用于在查询中将两个或多个表中的行基于它们之间的关联条件进行匹配。JOIN操作允许您将来自不同表的相关数据组合到一起&#xff0c;以便一次性检索所有相关信息。JOIN操作通常涉及使用ON子句指定…

电文加密(C语言)

一、题目说明&#xff1b; 即第1个字母变成第26个字母&#xff0c;第i个字母变成第(26 - i 1)个字母&#xff0c;非字母字符不变。 二、N-S流程图&#xff1b; 三、运行结果&#xff1b; 四、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h&g…

C语言深入理解指针(4)--指针笔试题解析

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 C语言深入理解指针(4) 收录于专栏【C语言学习】 本专栏旨在分享学习C语言学习的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. size…

【机器学习300问】79、Mini-Batch梯度下降法的原理是什么?

Mini-Batch梯度下降法是一种将训练数据集分成小批次进行学习的优化方法&#xff0c;通过这种方式&#xff0c;可以有效地解决内存限制问题并加速学习过程。 一、为什么要使用Mini-Batch&#xff1f; 在机器学习尤其是深度学习中&#xff0c;我们常常面临海量数据处理的问题。如…

吴恩达 深度学习 神经网络 softmax adam 交叉验证

神经网络中的层&#xff1a;输入层&#xff08;layer 0&#xff09;、隐藏层、卷积层&#xff08;看情况用这个&#xff09;、输出层。&#xff08;参考文章&#xff09; 激活函数&#xff1a; 隐藏层一般用relu函数&#xff1b; 输出层根据需要&#xff0c;二分类用sigmoid&…

ExcelVBA在选择区域(有合并)中删除清除空行

【问题】 关于删除空行&#xff0c;以前是用函数来完成工作的&#xff0c; 今天有人提出问题&#xff0c;传来这个文件&#xff0c; 现有数据&#xff0c;1w多行&#xff0c;其中有部分列有不同合并单元格&#xff0c;跨行也不一样。如果要进行筛选删除空行&#xff0c;有一定的…

matlab使用教程(70)—修改坐标区属性

1.控制坐标轴长度比率和数据单位长度 您可以控制 x 轴、y 轴和 z 轴的相对长度&#xff08;图框纵横比&#xff09;&#xff0c;也可以控制一个数据单位沿每个轴的相对长度&#xff08;数据纵横比&#xff09;。 1.1图框纵横比 图框纵横比是 x 轴、y 轴和 z 轴的相对长度。默认…

【二叉树算法题记录】404. 左叶子之和

题目描述 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 题目分析 其实这题无论是迭代法还是递归法&#xff0c;最重要的是要明确判断左叶子的条件&#xff1a;当前节点有左孩子&#xff0c;且这个左孩子没有它的左孩子和右孩子。 迭代法 感觉只要二叉树相关…

MySQL查询篇-连接查询

文章目录 inner joinleft join 和right join inner join 内连接是inner join &#xff0c;只返回两个表匹配的数据行。 select a.*,b.* from a inner join b on a.id b.aid; --等价于 select a.*,b.* from a join b on a.id b.aid;left join 和right join 左外连接和右外…

docker部署微服务项目

要部署微服务项目&#xff0c;可以使用Docker来完成。Docker是一种容器化技术&#xff0c;可以将各个微服务打包成独立的容器&#xff0c;并且在同一个Host上运行。 下面是步骤&#xff1a; 安装Docker。根据你的操作系统选择相应的Docker版本&#xff0c;并按照官方文档进行安…

排序算法 下

1.快速排序 快速排序是Hoare在1962年提出的一种二叉树结构的交换排序的方法&#xff0c;采用了递归的思想 思想&#xff1a;任取待排序的元素序列中的某元素作为基准值&#xff0c;按照原来的顺序将序列分为两个子序列&#xff0c; 左子序列中的所有元素均小于基准直&#x…

Python-VBA函数之旅-sum函数

目录 一、sum函数的常见应用场景 二、sum函数使用注意事项 三、如何用好sum函数&#xff1f; 1、sum函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、sum函数的常…

CSS:盒子模型

目录 ▐ box—model概述 ▐ 盒子的组成 ▐ 内容区 ▐ 内边距 ▐ 边框 ▐ 外边距 ▐ 清除浏览器默认样式 ▐ box—model概述 • CSS处理网页时&#xff0c;它认为每个标签都包含在一个不可见的盒子里. • 如果把所有的标签都想象成盒子&#xff0c;那么我们对网…

如何判断一个元素是否在可视区域中

可视区域就是我们浏览网页的设备肉眼可见的区域。 在开发总&#xff0c;我们经常需要判断目标元素是否在可视区域内或者可视区域的距离小于一个值&#xff0c;从而实现一些常用的功能&#xff0c;比如&#xff1a; 图片懒加载列表的无限滚动计算广告元素的曝光情况可点击链接…

记一次requests.get()返回数据乱码问题

现象 使用requests.get()请求&#xff0c;添加了header, 返回的数据使用了text接收&#xff1b;打印出来发现是乱码&#xff1b; 尝试解决 1、 设置encoding ret requests.get(url,headersheaders).text ret.encoding utf-8方法不生效&#xff1b; 2、利用apparent_enco…

远程桌面如何连接?

远程桌面连接是一种可以在不同地点之间共享电脑桌面的技术。通过远程桌面连接&#xff0c;用户可以在远程的计算机上操作另一台计算机&#xff0c;就像是直接坐在前者的前面一样。这种技术可以帮助用户解决在不同地点之间共享数据、协同办公、设备管理等问题。 【天联】的使用场…

【DevOps】linux命令top详解和实例

目录 一、top 命令基本用法 二、top 的输出解读 解释各部分信息 三、交互命令 四、实用示例 1、基本使用 2、按 CPU 使用率排序 3、 按内存使用率排序 4、监控特定用户的进程 5、实时查看特定 PID 的进程 6、调整屏幕刷新间隔 7、显示批处理模式 8、使用配置文件 …