React 18 使用 Context 深层传递参数

news2024/11/25 18:46:53

参考文章

使用 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>
      <Post
        title="旅行者,你好!"
        body="来看看我的冒险。"
      />
      <AllPosts />
    </Section>
  );
}

function AllPosts() {
  return (
    <Section>
      <Heading>帖子</Heading>
      <RecentPosts />
    </Section>
  );
}

function RecentPosts() {
  return (
    <Section>
      <Heading>最近的帖子</Heading>
      <Post
        title="里斯本的味道"
        body="...那些蛋挞!"
      />
      <Post
        title="探戈节奏中的布宜诺斯艾利斯"
        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.coloradmin.cn/o/978877.html

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

相关文章

智能合约安全分析,Vyper 重入锁漏洞全路径分析

智能合约安全分析&#xff0c;Vyper 重入锁漏洞全路径分析 事件背景 7 月 30 日 21:10 至 7 月 31 日 06:00 链上发生大规模攻击事件&#xff0c;导致多个 Curve 池的资金损失。漏洞的根源都是由于特定版本的 Vyper 中出现的重入锁故障。 攻击分析 通过对链上交易数据初步分…

高速人工智能无人机首次击败世界冠军赛车手

大学创造了第一个能够在无人机比赛中击败人类的自主系统。 周三&#xff0c;苏黎世大学和英特尔公司的一组研究人员宣布的他们开发了一个名为Swift的自主无人机系统&#xff0c;可以在第一人称视角下击败人类冠军(FPV)无人驾驶赛车。虽然人工智能以前在像国际象棋这样的游戏中击…

软件测试/测试开发丨建立质量保障体系,软件质量提升90%!原来是这个秘诀...

在现代软件开发领域&#xff0c;质量保障一直是备受争议的话题。关于测试角色在软件全流程中的价值、是否存在一套软件测试方法论以及如何衡量质量和效率的问题一直困扰着业界。为了能让大家更深入的学习质量保障体系&#xff0c;霍格沃兹测试开发学社邀请了大厂的资深测试经理…

modprobe命令及其与insmod depmod的区别

1. modprobe命令详解 modprobe工具可以智能的添加和删除一个模块&#xff0c;之所以说它智能&#xff0c;是因为它能够通过配置的一些预定义的规则解析出模块之间的依赖关系&#xff0c;并且自动加载依赖的模块。 modprobe会从 /lib/modules/uname -r目录中查找要加载的模块以…

Nginx从安装到使用,反向代理,负载均衡

什么是Nginx&#xff1f; 文章目录 什么是Nginx&#xff1f;1、Nginx概述1.1、Nginx介绍1.2、Nginx下载和安装1.3、Nginx目录结构 2、Nginx命令2.1、查看版本2.2、检查配置文件正确性2.3、启动和停止2.4、重新加载配置文件2.5、环境变量的配置 3、Nginx配置文件结构4、Nginx具体…

面向更大屏幕的片段

目前为止&#xff0c;只做过小屏幕设备运行应用。 本文中将创建灵活的用户界面&#xff0c;根据运行应用的设备让应用有不同的外观和行为。 之前我们创建了在手机上运行的Workout应用版本。但是在一个平板上运行这个应用时&#xff0c;应用的表现几乎是一样的。不过由于屏幕更大…

2023年数维杯数学建模A题河流-地下水系统水体污染研求解全过程文档及程序

2023年数维杯数学建模 A题 河流-地下水系统水体污染研 原题再现&#xff1a; 河流对地下水有着直接地影响&#xff0c;当河流补给地下水时&#xff0c;河流一旦被污染&#xff0c;容易导致地下水以及紧依河流分布的傍河水源地将受到不同程度的污染&#xff0c;这将严重影响工…

STM32 CAN快速配置(HAL库版本)

STM32 CAN快速配置&#xff08;HAL库版本&#xff09; 目录 STM32 CAN快速配置&#xff08;HAL库版本&#xff09;前言1 软件编程1.1 初始化1.1.1 引脚设置1.1.2 CAN参数设置1.1.3 CAN滤波器设置 1.2 CAN发送1.3 CAN接收 2 运行测试结束语 前言 控制器局域网总线&#xff08;CA…

vscode debug python launch.json添加args不起作用

问题 为了带入参数调试python 程序&#xff0c;按照网上搜到的教程配置了lauch.json文件&#xff0c;文件中添加了"args": [“model” “0” “path”] {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: h…

恢复iTunes备份看这里,2招教你搞定!

iTunes除了是一款免费的数字媒体播放程序以外&#xff0c;苹果用户还可以借助iTunes对自己的iPhone进行全面的备份和恢复&#xff0c;并且在设备损坏或者数据&#xff0c;也能够帮助用户快速恢复数据。当您的数据意外丢失后&#xff0c;该如何从itunes备份中恢复数据呢&#xf…

Shell 运算符及语法结构

目录 一、Shell运算符 1.1 表达式expr 1.2 运算操作 1.3 操作实例 二、Shell条件判断 2.1 基本语法 2.2 值、权限、类型、多条件判断 三、Shell流程控制 3.1 if 流程语法 3.2 case 流程语法 3.3 for 流程语法 3.4 内部运算符 3.5 while循环流程语法 四、Shell读…

香港物流公司新世纪储运申请1125万美元美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;香港物流公司新世纪储运New Century Logistics (BVI) Limited&#xff0c;近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票…

865. 具有所有最深节点的最小子树(javascript)865. Smallest Subtree with all the Deepest Nodes

给定一个根为 root 的二叉树&#xff0c;每个节点的深度是 该节点到根的最短距离 。 返回包含原始树中所有 最深节点 的 最小子树 。 如果一个节点在 整个树 的任意节点之间具有最大的深度&#xff0c;则该节点是 最深的 。 一个节点的 子树 是该节点加上它的所有后代的集合…

【C++学习笔记】4、变量

文章目录 【 1、变量的定义 】【 2、变量的声明 】示例 【 3、左值和右值 】 变量&#xff1a;相当于是程序可操作的数据存储区的名称。在 C 中&#xff0c;有多种变量类型可用于存储不同种类的数据。C 中每个变量都有指定的类型&#xff0c;类型决定了变量存储的大小和布局&am…

动态规划之简单多状态

简单多状态 1. 按摩师&#xff08;easy&#xff09;2. 打家劫舍II &#xff08;medium&#xff09;3. 删除并获得点数&#xff08;medium&#xff09;4. 买卖股票的最佳时机含冷冻期&#xff08;medium&#xff09;5. 买卖股票的最佳时机III&#xff08;hard&#xff09; 1. 按…

《人生苦短,我学Python》——条件判断->(if-elif-else)多向选择 条件嵌套

今天&#xff0c;我们来学习多向选择&#xff01;if--elif--else if 后的语句是当 if 判断条件成立时&#xff0c;执行的操作。elif 后的语句是当 if 判断不成立时&#xff0c;再判断一次&#xff0c;如果成立&#xff0c;执行的操作。else 后的语句是当以上所有判断条件都不成…

数据库 设计规范数据库设计样例

目录 5 数据库5.1 数据库命名规范5.2 数据库字段命名5.2.1 字段命名规范5.2.2 命名规范5.2.3 待优化命名示例5.2.4 字段类型规范5.2.5数据库中每个字段的规范描述 5.3表设计5.4 参考设计5.4.1 应用场景5.4.2 需求分析5.4.3 设计思路5.4.4 表结构设计5.4.5 缓存策略Q1 冗余设计和…

含面试题 Redis 为什么这么快?深度解析性能的奥秘超级用心的图文版

面试题分享 2023最新面试合集链接 2023大厂面试题PDF 面试题PDF版本 java、python面试题 项目实战:AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮AI想象空间 史上最全文档AI绘画stab…

ICCV 2023|通过慢学习和分类器对齐在预训练模型上进行持续学习

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 作者介绍 张耕维 悉尼科技大学在读博士生&#xff0c;研究方向为持续学习 报告题目 通过慢学习和分类器对齐在预训练模型上进行持续学习 内容简介 持续学习研究的目标在于提高模型利用顺序到达的数据进行学习的…

【python基础知识】6.布尔值和四种语句(break、continue、pass、else)

文章目录 前言用数据做判断&#xff1a;布尔值两个数值做比较直接用数值做运算布尔值之间的运算 四种新的语句break语句continue语句pass语句else语句循环小练习 前言 Hi&#xff0c;你来了。 上一关我们学习了for循环和while循环&#xff0c;让我们复习回顾一下&#xff1a;…