React 18 使用 Context 深层传递参数

参考文章

使用 Context 深层传递参数

通常来说,会通过 props 将信息从父组件传递到子组件。但是,如果必须通过许多中间组件向下传递 props,或是在应用中的许多组件需要相同的信息,传递 props 会变的十分冗长和不便。Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。

传递 props 带来的问题

传递 props 是将数据通过 UI 树显式传递到使用它的组件的好方法。

但是当需要在组件树中深层传递参数以及需要在组件间复用相同的参数时,传递 props 就会变得很麻烦。最近的根节点父组件可能离需要数据的组件很远,状态提升 到太高的层级会导致 “逐层传递 props” 的情况。

在这里插入图片描述

React 的 context 功能可以在组件树中不需要 props 将数据“直达”到所需的组件中。

Context:传递 props 的另一种方法

Context 让父组件可以为它下面的整个组件树提供数据。Context 有很多种用途。这里就有一个示例。思考一下这个 Heading 组件接收一个 level 参数来决定它标题尺寸的场景:

// App.js
import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section><Heading level={1}>主标题</Heading><Heading level={2}>副标题</Heading><Heading level={3}>子标题</Heading><Heading level={4}>子子标题</Heading></Section>);
}
// Section.js
export default function Section({ children }) {return (<section className="section">{children}</section>);
}
//  Heading.js
export default function Heading({ level, children }) {switch (level) {case 1:return <h1>{children}</h1>;case 2:return <h2>{children}</h2>;case 3:return <h3>{children}</h3>;case 4:return <h4>{children}</h4>;default:throw Error('未知的 level:' + level);}
}

假设想让相同 Section 中的多个 Heading 具有相同的尺寸:

import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section><Heading level={1}>主标题</Heading><Section><Heading level={2}>副标题</Heading><Heading level={2}>副标题</Heading><Section><Heading level={3}>子标题</Heading><Heading level={3}>子标题</Heading><Section><Heading level={4}>子子标题</Heading><Heading level={4}>子子标题</Heading></Section></Section></Section></Section>);
}

目前,将 level 参数分别传递给每个 <Heading>

<Section><Heading level={3}>关于</Heading><Heading level={3}>照片</Heading>
</Section>

level 参数传递给 <Section> 组件而不是传给 <Heading> 组件看起来更好一些。这样的话可以强制使同一个 section 中的所有标题都有相同的尺寸:

<Section level={3}><Heading>关于</Heading><Heading>照片</Heading>
</Section>

但是 <Heading> 组件是如何知道离它最近的 <Section> 的 level 的呢?

这需要子组件可以通过某种方式“访问”到组件树中某处在其上层的数据。

不能只通过 props 来实现它。这就是 context 大显身手的地方。可以通过以下三个步骤来实现它:

  1. 创建 一个 context。(可以将其命名为 LevelContext, 因为它表示的是标题级别。)
  2. 在需要数据的组件内 使用 刚刚创建的 context。(Heading 将会使用 LevelContext。)
  3. 在指定数据的组件中 提供 这个 context。 (Section 将会提供 LevelContext。)

Context 可以让父节点,甚至是很远的父节点都可以为其内部的整个组件树提供数据

在这里插入图片描述

Step 1:创建 context

首先,需要创建这个 context,并 将其从一个文件中导出,这样组件才可以使用它:

// LevelContext.js
import { createContext } from 'react';export const LevelContext = createContext(1);

createContext 只需默认值这么一个参数。在这里, 1 表示最大的标题级别,但是可以传递任何类型的值(甚至可以传入一个对象)。将在下一个步骤中见识到默认值的意义。

Step 2:使用 Context

从 React 中引入 useContext Hook 以及刚刚创建的 context:

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

目前,Heading 组件从 props 中读取 level

export default function Heading({ level, children }) {// ...
}

删掉 level 参数并从刚刚引入的 LevelContext 中读取值:

export default function Heading({ children }) {const level = useContext(LevelContext);// ...
}

useContext 是一个 Hook。和 useState 以及 useReducer一样,只能在 React 组件中(不是循环或者条件里)立即调用 Hook。useContext 告诉 React Heading 组件想要读取 LevelContext

现在 Heading 组件没有 level 参数,不需要再像这样在 JSX 中将 level 参数传递给 Heading

<Section><Heading level={4}>子子标题</Heading><Heading level={4}>子子标题</Heading>
</Section>

修改一下 JSX,让 Section 组件代替 Heading 组件接收 level 参数:

<Section level={4}><Heading>子子标题</Heading><Heading>子子标题</Heading>
</Section>

将修改下边的代码直到它正常运行:

import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section level={1}><Heading>主标题</Heading><Section level={2}><Heading>副标题</Heading><Heading>副标题</Heading><Section level={3}><Heading>子标题</Heading><Heading>子标题</Heading><Section level={4}><Heading>子子标题</Heading><Heading>子子标题</Heading></Section></Section></Section></Section>);
}

注意:这个示例还不能运行。所有 headings 的尺寸都一样,因为 即使正在使用 context,但是还没有提供它。 React 不知道从哪里获取这个 context!

如果不提供 context,React 会使用在上一步指定的默认值。在这个例子中,为 createContext 传入了 1 这个参数,所以 useContext(LevelContext) 会返回 1,把所有的标题都设置为<h1>。通过让每个 Section 提供它自己的 context 来修复这个问题。

Step 3:提供 context

Section 组件目前渲染传入它的子组件:

export default function Section({ children }) {return (<section className="section">{children}</section>);
}

把它们用 context provider 包裹起来 以提供 LevelContext 给它们:

import { LevelContext } from './LevelContext.js';export default function Section({ level, children }) {return (<section className="section"><LevelContext.Provider value={level}>{children}</LevelContext.Provider></section>);

这告诉 React:“如果在 <Section> 组件中的任何子组件请求 LevelContext,给他们这个 level。”组件会使用 UI 树中在它上层最近的那个 <LevelContext.Provider> 传递过来的值。

import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section level={1}><Heading>主标题</Heading><Section level={2}><Heading>副标题</Heading><Heading>副标题</Heading><Section level={3}><Heading>子标题</Heading><Heading>子标题</Heading><Section level={4}><Heading>子子标题</Heading><Heading>子子标题</Heading></Section></Section></Section></Section>);
}

这与原始代码的运行结果相同,但是不需要向每个 Heading 组件传递 level 参数了!取而代之的是,它通过访问上层最近的 Section 来“断定”它的标题级别:

  1. 将一个 level 参数传递给 <Section>
  2. Section 把它的子元素包在 <LevelContext.Provider value={level}> 里面。
  3. Heading 使用 useContext(LevelContext) 访问上层最近的 LevelContext 提供的值。

在相同的组件中使用并提供 context

目前,仍需要手动指定每个 section 的 level

export default function Page() {return (<Section level={1}>...<Section level={2}>...<Section level={3}>...

由于 context 可以从上层的组件读取信息,每个 Section 都会从上层的 Section 读取 level,并自动向下层传递 level + 1。 可以像下面这样做:

// Section.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Section({ children }) {const level = useContext(LevelContext);return (<section className="section"><LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider></section>);
}

这样修改之后,不用将 level 参数传给 <Section> 或者是 <Heading> 了:

// App.js
import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section><Heading>主标题</Heading><Section><Heading>副标题</Heading><Heading>副标题</Heading><Section><Heading>子标题</Heading><Heading>子标题</Heading><Section><Heading>子子标题</Heading><Heading>子子标题</Heading></Section></Section></Section></Section>);
}
// Heading.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Heading({ children }) {const level = useContext(LevelContext);switch (level) {case 0:throw Error('Heading 必须在 Section 内部!');case 1:return <h1>{children}</h1>;case 2:return <h2>{children}</h2>;case 3:return <h3>{children}</h3>;case 4:return <h4>{children}</h4>;default:throw Error('未知的 level:' + level);}
}

现在,HeadingSection 都通过读取 LevelContext 来判断它们的深度。而且 Section 把它的子组件都包在 LevelContext 中来指定其中的任何内容都处于一个“更深”的级别。

注意:本示例使用标题级别来展示,因为它们直观地显示了嵌套组件如何覆盖 context。但是 context 对于许多其他的场景也很有用。可以用它来传递整个子树需要的任何信息:当前的颜色主题、当前登录的用户等。

Context 会穿过中间层级的组件

可以在提供 context 的组件和使用它的组件之间的层级插入任意数量的组件。这包括像 <div> 这样的内置组件和自己创建的组件。

在这个示例中,相同的 Post 组件(带有虚线边框)在两个不同的嵌套层级上渲染。注意,它内部的 <Heading> 会自动从最近的 <Section> 获取它的级别:

// App.js
import Heading from './Heading.js';
import Section from './Section.js';export default function ProfilePage() {return (<Section><Heading>My Profile</Heading><Posttitle="旅行者,你好!"body="来看看我的冒险。"/><AllPosts /></Section>);
}function AllPosts() {return (<Section><Heading>帖子</Heading><RecentPosts /></Section>);
}function RecentPosts() {return (<Section><Heading>最近的帖子</Heading><Posttitle="里斯本的味道"body="...那些蛋挞!"/><Posttitle="探戈节奏中的布宜诺斯艾利斯"body="我爱它!"/></Section>);
}function Post({ title, body }) {return (<Section isFancy={true}><Heading>{title}</Heading><p><i>{body}</i></p></Section>);
}
// Section.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Section({ children, isFancy }) {const level = useContext(LevelContext);return (<section className={'section ' +(isFancy ? 'fancy' : '')}><LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider></section>);
}

不需要做任何特殊的操作。Section 为它内部的树指定一个 context,所以可以在任何地方插入一个 <Heading>,而且它会有正确的尺寸。

Context 让你可以编写“适应周围环境”的组件,并且根据 在哪 (或者说 在哪个 context 中)来渲染它们不同的样子。

Context 的工作方式可能会让你想起 CSS 属性继承。在 CSS 中,可以为一个 <div> 手动指定 color: blue,并且其中的任何 DOM 节点,无论多深,都会继承那个颜色,除非中间的其他 DOM 节点用 color: green 来覆盖它。类似地,在 React 中,覆盖来自上层的某些 context 的唯一方法是将子组件包裹到一个提供不同值的 context provider 中

在 CSS 中,诸如 colorbackground-color 之类的不同属性不会覆盖彼此。可以设置所有 <div>color 为红色,而不会影响 background-color。类似地,不同的 React context 不会覆盖彼此。通过 createContext() 创建的每个 context 都和其他 context 完全分离,只有使用和提供 那个特定的 context 的组件才会联系在一起。一个组件可以轻松地使用或者提供许多不同的 context。

写在使用 context 之前

使用 Context 看起来非常诱人!然而,这也意味着它也太容易被过度使用了。如果只想把一些 props 传递到多个层级中,这并不意味着需要把这些信息放到 context 里。

在使用 context 之前,可以考虑以下几种替代方案:

  1. 从 传递 props 开始。 如果组件看起来不起眼,那么通过十几个组件向下传递一堆 props 并不罕见。这有点像是在埋头苦干,但是这样做可以让哪些组件用了哪些数据变得十分清晰!维护你代码的人会很高兴你用 props 让数据流变得更加清晰。
  2. 抽象组件并 将 JSX 作为 children 传递 给它们。 如果通过很多层不使用该数据的中间组件(并且只会向下传递)来传递数据,这通常意味着在此过程中忘记了抽象组件。举个例子,可能想传递一些像 posts 的数据 props 到不会直接使用这个参数的组件,类似 <Layout posts={posts} />。取而代之的是,让 Layoutchildren 当做一个参数,然后渲染 <Layout><Posts posts={posts} /></Layout>。这样就减少了定义数据的组件和使用数据的组件之间的层级。

如果这两种方法都不适合,再考虑使用 context。

Context 的使用场景

  • 主题: 如果应用允许用户更改其外观(例如暗夜模式),可以在应用顶层放一个 context provider,并在需要调整其外观的组件中使用该 context。
  • 当前账户: 许多组件可能需要知道当前登录的用户信息。将它放到 context 中可以方便地在树中的任何位置读取它。某些应用还允许同时操作多个账户(例如,以不同用户的身份发表评论)。在这些情况下,将 UI 的一部分包裹到具有不同账户数据的 provider 中会很方便。
  • 路由: 大多数路由解决方案在其内部使用 context 来保存当前路由。这就是每个链接“知道”它是否处于活动状态的方式。如果你创建自己的路由库,你可能也会这么做。
  • 状态管理: 随着应用的增长,最终在靠近应用顶部的位置可能会有很多 state。许多遥远的下层组件可能想要修改它们。通常 将 reducer 与 context 搭配使用来管理复杂的状态并将其传递给深层的组件来避免过多的麻烦。

Context 不局限于静态值。如果在下一次渲染时传递不同的值,React 将会更新读取它的所有下层组件!这就是 context 经常和 state 结合使用的原因。

一般而言,如果树中不同部分的远距离组件需要某些信息,context 将会对你大有帮助。

摘要

  • Context 使组件向其下方的整个树提供信息。
  • 传递 Context 的方法:
    1. 通过 export const MyContext = createContext(defaultValue) 创建并导出 context。
    2. 在无论层级多深的任何子组件中,把 context 传递给 useContext(MyContext) Hook 来读取它。
    3. 在父组件中把 children 包在 <MyContext.Provider value={...}> 中来提供 context。
  • Context 会穿过中间的任何组件。
  • Context 可以让你写出 “较为通用” 的组件。
  • 在使用 context 之前,先试试传递 props 或者将 JSX 作为 children 传递。

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

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

相关文章

Web后端开发(请求响应)上

请求响应的概述 浏览器&#xff08;请求&#xff09;<--------------------------(HTTP协议)---------------------->&#xff08;响应&#xff09;Web服务器 请求&#xff1a;获取请求数据 响应&#xff1a;设置响应数据 BS架构&#xff1a;浏览器/服务器架构模式。…

阿里云部署开源MQTT平台mosquitto的docker操作

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的消息传输协议&#xff0c;广泛用于物联网和传感器网络中。Mosquitto是一个流行的开源MQTT代理&#xff0c;可以在Docker中进行配置和部署。本文将详细介绍如何在Docker中配置Mosquitto MQTT代理…

电梯SIP-IP五方对讲管理系统

电梯SIP-IP五方对讲管理系统 是深圳锐科达精心打磨的一款IP数字信号对讲设备&#xff0c;是在传统电梯对讲系统基础上的一次全新升级&#xff0c;突破了模拟、FM调频系统存在的技术障碍&#xff0c;实现联网;在模/数交替的过程中&#xff0c;继承了模拟、FM调频系统的优点&…

Python实现猎人猎物优化算法(HPO)优化卷积神经网络回归模型(CNN回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

Krahets 笔面试精选 88 题——40. 组合总和 II

使用深度搜索的方法&#xff1a; 由于题目说候选数组中的每个数字在每个组合只能出现一次&#xff0c;所以&#xff0c;为了避免重复&#xff0c;在开始之前对候选数组进行升序排序&#xff0c;这样优先选择小的数&#xff0c;如果当前的数都小于目标值&#xff0c;则后面的数就…

Android MQTT:实现设备信息上报与远程控制

Android MQTT&#xff1a;实现设备信息上报与远程控制 1. 介绍 1.1 MQTT是什么&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的消息传输协议&#xff0c;最初由IBM开发&#xff0c;用于连接远程设备与服务器之间的通信。它在物…

day2_C++

day2_C 代码题思维导图 代码题 #include using namespace std;#define MAX 50struct StuData {private:int scoreArr[MAX];int num;public:void setNum(int num);void input();void sort();void show();int getnum();};void StuData::setNum(int num){this->num num; }vo…

第3章 【MySQL】字符集和比较规则

3.1 字符集和比较规则简介 3.1.1 字符集简介 如何存储字符串&#xff1f;需要建立字符与二进制数据的映射关系。建立这个关系需要&#xff1a; 1.把哪些字符映射成二进制数据&#xff1f; 2.怎么映射&#xff1f; 将一个字符映射成一个二进制数据的过程也叫做 编码 &#…

[杂谈]-快速了解直接内存访问 (DMA)

快速了解直接内存访问 (DMA) 文章目录 快速了解直接内存访问 (DMA)1、使用 DMA 需要什么&#xff1f;2、DMA介绍3、DMA 中的数据传输如何进行&#xff1f;4、DMA接口5、DMAC 控制器寄存器6、DMA 控制器编程模式6.1 突发模式&#xff08;Burst Mode&#xff09;6.2 循环窃取模式…

MATLAB 动态图GIF

MATLAB 动态图GIF 前言一、创建动态图&#xff08;动态曲线、动态曲面&#xff09;1. 创建动画曲线&#xff08;MATLAB animatedline函数&#xff09;2. 创建动画曲面 二. 保存动态图三、完整示例1. 动态曲线&#xff08; y s i n ( x ) ysin(x) ysin(x)&#xff09;2. 动态曲…

Linux之权限

目录 一、shell运行原理 二、权限 1、对人操作 2、对角色和文件操作 修改权限&#xff08;改属性&#xff09;&#xff1a; ①ugo- ②二进制数的表示 修改权限&#xff08;改人&#xff09;&#xff1a; 三、权限的相关问题 1、目录的权限 2、umask 3、粘滞位 一、s…

LeetCode 202 快乐数

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 法一&#xff1a;哈希 使用哈希表循环判断每次经过平方和的数&#xff0c;如果为1则直接返回true&#xff0c;若之前存在过但不为1则直接返回false 代码 class Solution { public:// 计算…

基于文本提示的图像目标检测与分割实践

近年来&#xff0c;计算机视觉取得了显着的进步&#xff0c;特别是在图像分割和目标检测任务方面。 最近值得注意的突破之一是分段任意模型&#xff08;SAM&#xff09;&#xff0c;这是一种多功能深度学习模型&#xff0c;旨在有效地从图像和输入提示中预测对象掩模。 通过利用…

【业务功能篇92】微服务-springcloud-多线程-异步处理-异步编排-CompletableFutrue

三、CompletableFutrue 一个商品详情页 展示SKU的基本信息 0.5s展示SKU的图片信息 0.6s展示SKU的销售信息 1sspu的销售属性 1s展示规格参数 1.5sspu详情信息 1s 1.ComplatableFuture介绍 Future是Java 5添加的类&#xff0c;用来描述一个异步计算的结果。你可以使用 isDone方…

Ansible自动化运维之playbooks剧本

文章目录 一.playbooks介绍1.playbooks简述2.playbooks剧本格式3.playbooks组成部分4.运行playbooks及检测文件配置 二.模块实战实例1.playbooks模块实战实例2.vars模块实战实例3.指定远程主机sudo切换用户4.when模块实战实例5.with_items迭代模块实战实例6.Templates 模块实战…

Delft3D模型教程

详情点击公众号链接&#xff1a;基于Delft3D模型水体流动、污染物对流扩散、质点运移、溢油漂移及地表水环境报告编制教程 Delft3D计算网格构建 Delft3D水动力数值模拟 Delft3D污染物对流扩散数值模拟 一&#xff0c;Delft3D软件及建模原理和步骤 1.1地表水数值模拟常用软件、…

认识SQL sever

目录 一、数据库的概念 1.1数据库的基本概念 1.2对数据库的了解 二、数据库的分类 2.1关系型数据库&#xff08;RDBMS&#xff09;&#xff1a; 2.2非关系型数据库&#xff08;NoSQL&#xff09;&#xff1a; 2.3混合数据库&#xff1a; 2.4数据仓库&#xff1a; 2.5嵌…

报错:为什么数组明明有内容但打印的length是0

文章目录 一、问题二、分析三、解决1.将异步改为同步2.设置延迟 一、问题 在日常开发中&#xff0c;for 循环遍历调用接口&#xff0c;并将接口返回的值进行拼接&#xff0c;即push到一个新的数组中&#xff0c;但是在for循环内部是可以拿到这个新的数组&#xff0c;而for循环…

docker笔记9:Docker-compose容器编排

目录 1.是什么&#xff1f; 2. 能干嘛&#xff1f; 3.去哪下&#xff1f; 4.安装步骤 ​编辑 5.卸载步骤 6.Compose核心概念 6.1概念 6.2 Compose常用命令 7.Compose编排微服务 7.1改造升级微服务工程docker_boot 7.2不用Compose 7.2.1 单独的mysql容器实例 7.3 …

自动化测试基础知识详解

前言 有颜色的标注主要是方便记忆&#xff0c;勾选出个人感觉的重点 块引用&#xff1a;大部分是便于理解的话&#xff0c;稍微看看就行&#xff0c;主要是和正常的文字进行区分的 1、什么是自动化测试 自动化测试是软件测试活动中一个重要分支和组成部分&#xff0c;随着软…