通常来说,会通过 props 将信息从父组件传递到子组件。但是,如果必须通过许多中间组件向下传递 props,或是在应用中的许多组件需要相同的信息,传递 props 会变的十分冗长和不便。Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。
传递 props 是将数据通过 UI 树显式传递到使用它的组件的好方法。
但是当需要在组件树中深层传递参数以及需要在组件间复用相同的参数时,传递 props 就会变得很麻烦。最近的根节点父组件可能离需要数据的组件很远,状态提升 到太高的层级会导致 “逐层传递 props” 的情况。
React 的 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
参数分别传递给每个
:
<Section>
<Heading level={3}>关于</Heading>
<Heading level={3}>照片</Heading>
</Section>
将 level
参数传递给 组件而不是传给
组件看起来更好一些。这样的话可以强制使同一个 section 中的所有标题都有相同的尺寸:
<Section level={3}>
<Heading>关于</Heading>
<Heading>照片</Heading>
</Section>
但是
组件是如何知道离它最近的 的 level 的呢?
这需要子组件可以通过某种方式“访问”到组件树中某处在其上层的数据。
不能只通过 props 来实现它。这就是 context 大显身手的地方。可以通过以下三个步骤来实现它:
LevelContext
, 因为它表示的是标题级别。)Heading
将会使用 LevelContext
。)Section
将会提供 LevelContext
。)Context 可以让父节点,甚至是很远的父节点都可以为其内部的整个组件树提供数据。
首先,需要创建这个 context,并 将其从一个文件中导出,这样组件才可以使用它:
// LevelContext.js
import { createContext } from 'react';
export const LevelContext = createContext(1);
createContext
只需默认值这么一个参数。在这里, 1
表示最大的标题级别,但是可以传递任何类型的值(甚至可以传入一个对象)。将在下一个步骤中见识到默认值的意义。
从 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
,把所有的标题都设置为。通过让每个
Section
提供它自己的 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:“如果在 组件中的任何子组件请求
LevelContext
,给他们这个 level
。”组件会使用 UI 树中在它上层最近的那个
传递过来的值。
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
来“断定”它的标题级别:
level
参数传递给
。Section
把它的子元素包在
里面。Heading
使用 useContext(LevelContext)
访问上层最近的 LevelContext
提供的值。目前,仍需要手动指定每个 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
参数传给 或者是
了:
// 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);
}
}
现在,Heading
和 Section
都通过读取 LevelContext
来判断它们的深度。而且 Section
把它的子组件都包在 LevelContext
中来指定其中的任何内容都处于一个“更深”的级别。
注意:本示例使用标题级别来展示,因为它们直观地显示了嵌套组件如何覆盖 context。但是 context 对于许多其他的场景也很有用。可以用它来传递整个子树需要的任何信息:当前的颜色主题、当前登录的用户等。
可以在提供 context 的组件和使用它的组件之间的层级插入任意数量的组件。这包括像 在这个示例中,相同的 不需要做任何特殊的操作。 Context 让你可以编写“适应周围环境”的组件,并且根据 在哪 (或者说 在哪个 context 中)来渲染它们不同的样子。 Context 的工作方式可能会让你想起 CSS 属性继承。在 CSS 中,可以为一个 在 CSS 中,诸如 使用 Context 看起来非常诱人!然而,这也意味着它也太容易被过度使用了。如果只想把一些 props 传递到多个层级中,这并不意味着需要把这些信息放到 context 里。 在使用 context 之前,可以考虑以下几种替代方案: 如果这两种方法都不适合,再考虑使用 context。 Context 不局限于静态值。如果在下一次渲染时传递不同的值,React 将会更新读取它的所有下层组件!这就是 context 经常和 state 结合使用的原因。 一般而言,如果树中不同部分的远距离组件需要某些信息,context 将会对你大有帮助。Post
组件(带有虚线边框)在两个不同的嵌套层级上渲染。注意,它内部的
会自动从最近的 获取它的级别:
// 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,所以可以在任何地方插入一个
,而且它会有正确的尺寸。color: blue
,并且其中的任何 DOM 节点,无论多深,都会继承那个颜色,除非中间的其他 DOM 节点用 color: green
来覆盖它。类似地,在 React 中,覆盖来自上层的某些 context 的唯一方法是将子组件包裹到一个提供不同值的 context provider 中。
color
和 background-color
之类的不同属性不会覆盖彼此。可以设置所有 color
为红色,而不会影响 background-color
。类似地,不同的 React context 不会覆盖彼此。通过 createContext()
创建的每个 context 都和其他 context 完全分离,只有使用和提供 那个特定的 context 的组件才会联系在一起。一个组件可以轻松地使用或者提供许多不同的 context。
写在使用 context 之前
children
传递 给它们。 如果通过很多层不使用该数据的中间组件(并且只会向下传递)来传递数据,这通常意味着在此过程中忘记了抽象组件。举个例子,可能想传递一些像 posts
的数据 props 到不会直接使用这个参数的组件,类似
。取而代之的是,让 Layout
把 children
当做一个参数,然后渲染
。这样就减少了定义数据的组件和使用数据的组件之间的层级。Context 的使用场景
摘要
export const MyContext = createContext(defaultValue)
创建并导出 context。useContext(MyContext)
Hook 来读取它。
中来提供 context。children
传递。
Android开发笔记(一百八十八)工作管理器WorkManager
5个被忽视的习惯,决定了你无法成为高级开发工程师
MySQL 的 C 语言接口
SQL 语法速成手册
新版外国人永居证身份证读卡器C++开发SDK接口
一款免费轻量级web报表工具
firefox_dev_linux下载安装配置(部分系统自带包请看结尾)
利用stream实现行政区域列表转tree树形结构
GPT+RPA用于自动流转分配,助力优化信访工作流程