使用上下文深入传递数据

通常,你会通过属性将信息从父组件传递到子组件。但是如果你必须在中间通过许多组件传递它们,或者如果你的应用中的许多组件需要相同的信息,那么传递属性会变得冗长和不方便。Context 允许父组件向其下面的树中的任何组件提供一些信息(无论多深),而无需通过属性显式传递。

¥Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. Context lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props.

你将学习到

  • 什么是 “属性钻取”

    ¥What “prop drilling” is

  • 如何用上下文替换重复的属性传递

    ¥How to replace repetitive prop passing with context

  • 上下文的常见用例

    ¥Common use cases for context

  • 上下文的常见替代方法

    ¥Common alternatives to context

传递属性的问题

¥The problem with passing props

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

¥Passing props is a great way to explicitly pipe data through your UI tree to the components that use it.

但是当你需要通过树深入传递一些属性时,或者如果许多组件需要相同的属性时,传递属性会变得冗长和不方便。最近的共同祖级可能远离需要数据的组件,那么高的 提升状态 可能导致称为 “属性钻取” 的情况。

¥But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and lifting state up that high can lead to a situation called “prop drilling”.

提升状态
Diagram with a tree of three components. The parent contains a bubble representing a value highlighted in purple. The value flows down to each of the two children, both highlighted in purple.
Diagram with a tree of three components. The parent contains a bubble representing a value highlighted in purple. The value flows down to each of the two children, both highlighted in purple.
属性钻井
Diagram with a tree of ten nodes, each node with two children or less. The root node contains a bubble representing a value highlighted in purple. The value flows down through the two children, each of which pass the value but do not contain it. The left child passes the value down to two children which are both highlighted purple. The right child of the root passes the value through to one of its two children - the right one, which is highlighted purple. That child passed the value through its single child, which passes it down to both of its two children, which are highlighted purple.
Diagram with a tree of ten nodes, each node with two children or less. The root node contains a bubble representing a value highlighted in purple. The value flows down through the two children, each of which pass the value but do not contain it. The left child passes the value down to two children which are both highlighted purple. The right child of the root passes the value through to one of its two children - the right one, which is highlighted purple. That child passed the value through its single child, which passes it down to both of its two children, which are highlighted purple.

如果有一种方法可以在不传递属性的情况下 “远距离传送” 数据给树中需要它的组件,那不是很好吗?有了 React 的上下文功能,就有了!

¥Wouldn’t it be great if there were a way to “teleport” data to the components in the tree that need it without passing props? With React’s context feature, there is!

上下文:传递属性的替代方案

¥Context: an alternative to passing props

上下文让父组件向其下方的整个树提供数据。上下文有很多用途。这是一个例子。考虑这个 Heading 组件,它的大小接受 level

¥Context lets a parent component provide data to the entire tree below it. There are many uses for context. Here is one example. Consider this Heading component that accepts a level for its size:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section>
      <Heading level={1}>Title</Heading>
      <Heading level={2}>Heading</Heading>
      <Heading level={3}>Sub-heading</Heading>
      <Heading level={4}>Sub-sub-heading</Heading>
      <Heading level={5}>Sub-sub-sub-heading</Heading>
      <Heading level={6}>Sub-sub-sub-sub-heading</Heading>
    </Section>
  );
}

假设你希望同一 Section 中的多个标题始终具有相同的大小:

¥Let’s say you want multiple headings within the same Section to always have the same size:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section>
      <Heading level={1}>Title</Heading>
      <Section>
        <Heading level={2}>Heading</Heading>
        <Heading level={2}>Heading</Heading>
        <Heading level={2}>Heading</Heading>
        <Section>
          <Heading level={3}>Sub-heading</Heading>
          <Heading level={3}>Sub-heading</Heading>
          <Heading level={3}>Sub-heading</Heading>
          <Section>
            <Heading level={4}>Sub-sub-heading</Heading>
            <Heading level={4}>Sub-sub-heading</Heading>
            <Heading level={4}>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

目前,你将 level 属性分别传递给每个 <Heading>

¥Currently, you pass the level prop to each <Heading> separately:

<Section>
<Heading level={3}>About</Heading>
<Heading level={3}>Photos</Heading>
<Heading level={3}>Videos</Heading>
</Section>

如果你可以将 level 属性传递给 <Section> 组件并将其从 <Heading> 中删除,那就太好了。这样你就可以强制同一部分中的所有标题具有相同的大小:

¥It would be nice if you could pass the level prop to the <Section> component instead and remove it from the <Heading>. This way you could enforce that all headings in the same section have the same size:

<Section level={3}>
<Heading>About</Heading>
<Heading>Photos</Heading>
<Heading>Videos</Heading>
</Section>

但是 <Heading> 组件如何知道其最接近的 <Section> 的级别呢?这将需要某种方式让子级 “询问” 获取树上方某处的数据。

¥But how can the <Heading> component know the level of its closest <Section>? That would require some way for a child to “ask” for data from somewhere above in the tree.

光靠属性是做不到的。这就是上下文发挥作用的地方。你将分三步完成:

¥You can’t do it with props alone. This is where context comes into play. You will do it in three steps:

  1. 创建一个上下文。(你可以将其称为 LevelContext,因为它用于标题级别。)

    ¥Create a context. (You can call it LevelContext, since it’s for the heading level.)

  2. 使用需要数据的组件中的上下文。(Heading 将使用 LevelContext。)

    ¥Use that context from the component that needs the data. (Heading will use LevelContext.)

  3. 从指定数据的组件提供上下文。(Section 将提供 LevelContext。)

    ¥Provide that context from the component that specifies the data. (Section will provide LevelContext.)

上下文让父级 - 即使是远方的父级! - 向其中的整棵树提供一些数据。

¥Context lets a parent—even a distant one!—provide some data to the entire tree inside of it.

在亲密的子级中使用上下文
Diagram with a tree of three components. The parent contains a bubble representing a value highlighted in orange which projects down to the two children, each highlighted in orange.
Diagram with a tree of three components. The parent contains a bubble representing a value highlighted in orange which projects down to the two children, each highlighted in orange.
在遥远的子级身上使用情境
Diagram with a tree of ten nodes, each node with two children or less. The root parent node contains a bubble representing a value highlighted in orange. The value projects down directly to four leaves and one intermediate component in the tree, which are all highlighted in orange. None of the other intermediate components are highlighted.
Diagram with a tree of ten nodes, each node with two children or less. The root parent node contains a bubble representing a value highlighted in orange. The value projects down directly to four leaves and one intermediate component in the tree, which are all highlighted in orange. None of the other intermediate components are highlighted.

步骤 1:创建上下文

¥Step 1: Create the context

首先,你需要创建上下文。你需要从文件中导出它,以便你的组件可以使用它:

¥First, you need to create the context. You’ll need to export it from a file so that your components can use it:

import { createContext } from 'react';

export const LevelContext = createContext(1);

createContext 的唯一参数是默认值。在这里,1 指的是最大的标题级别,但你可以传递任何类型的值(甚至是对象)。你将在下一步中看到默认值的重要性。

¥The only argument to createContext is the default value. Here, 1 refers to the biggest heading level, but you could pass any kind of value (even an object). You will see the significance of the default value in the next step.

步骤 2:使用上下文

¥Step 2: Use the context

从 React 和你的上下文中导入 useContext 钩子:

¥Import the useContext Hook from React and your context:

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

目前,Heading 组件从属性中读取 level

¥Currently, the Heading component reads level from props:

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

改为,删除 level 属性并从你刚刚导入的上下文中读取值,LevelContext

¥Instead, remove the level prop and read the value from the context you just imported, LevelContext:

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

useContext 是一个钩子。就像 useStateuseReducer 一样,你只能在 React 组件内立即调用钩子(不能在循环或条件内)。useContext 告诉 React Heading 组件想要读取 LevelContext

¥useContext is a Hook. Just like useState and useReducer, you can only call a Hook immediately inside a React component (not inside loops or conditions). useContext tells React that the Heading component wants to read the LevelContext.

现在 Heading 组件没有 level 属性,你不需要再像这样在你的 JSX 中将 level 属性传递给 Heading 了:

¥Now that the Heading component doesn’t have a level prop, you don’t need to pass the level prop to Heading in your JSX like this anymore:

<Section>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
</Section>

更新 JSX,使其成为接收它的 Section

¥Update the JSX so that it’s the Section that receives it instead:

<Section level={4}>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>

提醒一下,这是你试图开始工作的标记:

¥As a reminder, this is the markup that you were trying to get working:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section level={1}>
      <Heading>Title</Heading>
      <Section level={2}>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Section level={3}>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Section level={4}>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

请注意,此示例还不能正常工作!所有标题都具有相同的大小,因为即使你正在使用上下文,但你还没有提供它。React 不知道从哪里得到它!

¥Notice this example doesn’t quite work, yet! All the headings have the same size because even though you’re using the context, you have not provided it yet. React doesn’t know where to get it!

如果你不提供上下文,React 将使用你在上一步中指定的默认值。在此示例中,你将 1 指定为 createContext 的参数,因此 useContext(LevelContext) 返回 1,将所有这些标题设置为 <h1>。让我们通过让每个 Section 提供自己的上下文来解决这个问题。

¥If you don’t provide the context, React will use the default value you’ve specified in the previous step. In this example, you specified 1 as the argument to createContext, so useContext(LevelContext) returns 1, setting all those headings to <h1>. Let’s fix this problem by having each Section provide its own context.

步骤 3:提供上下文

¥Step 3: Provide the context

Section 组件当前渲染其子级:

¥The Section component currently renders its children:

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

用上下文提供程序封装它们以向它们提供 LevelContext

¥Wrap them with a context provider to provide the LevelContext to them:

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> 的值。

¥This tells React: “if any component inside this <Section> asks for LevelContext, give them this level.” The component will use the value of the nearest <LevelContext.Provider> in the UI tree above it.

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section level={1}>
      <Heading>Title</Heading>
      <Section level={2}>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Section level={3}>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Section level={4}>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

它与原始代码的结果相同,但你不需要将 level 属性传递给每个 Heading 组件!而是,它通过询问上面最接近的 Section 来 “弄清楚” 其标题级别:

¥It’s the same result as the original code, but you did not need to pass the level prop to each Heading component! Instead, it “figures out” its heading level by asking the closest Section above:

  1. 你将 level 属性传递给 <Section>

    ¥You pass a level prop to the <Section>.

  2. Section 将其子级封装到 <LevelContext.Provider value={level}> 中。

    ¥Section wraps its children into <LevelContext.Provider value={level}>.

  3. Heading 使用 useContext(LevelContext) 询问上面的 LevelContext 最接近的值。

    ¥Heading asks the closest value of LevelContext above with useContext(LevelContext).

从同一组件使用和提供上下文

¥Using and providing context from the same component

目前,你仍然需要手动指定每个部分的 level

¥Currently, you still have to specify each section’s level manually:

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

由于上下文允许你从上面的组件读取信息,因此每个 Section 都可以从上面的 Section 读取 level,并自动向下传递 level + 1。这是你可以如何做到的:

¥Since context lets you read information from a component above, each Section could read the level from the Section above, and pass level + 1 down automatically. Here is how you could do it:

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>

¥With this change, you don’t need to pass the level prop either to the <Section> or to the <Heading>:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section>
      <Heading>Title</Heading>
      <Section>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
        <Section>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Heading>Sub-heading</Heading>
          <Section>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
            <Heading>Sub-sub-heading</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

现在 HeadingSection 都读取了 LevelContext 来弄清楚它们有多 “深”。Section 将其子级封装到 LevelContext 中,以指定其中的任何内容都处于 “更深” 级别。

¥Now both Heading and Section read the LevelContext to figure out how “deep” they are. And the Section wraps its children into the LevelContext to specify that anything inside of it is at a “deeper” level.

注意

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

¥This example uses heading levels because they show visually how nested components can override context. But context is useful for many other use cases too. You can pass down any information needed by the entire subtree: the current color theme, the currently logged in user, and so on.

中间组件传递上下文

¥Context passes through intermediate components

你可以在提供上下文的组件和使用它的组件之间插入任意数量的组件。这包括内置组件(如 <div>)和你可能自己构建的组件。

¥You can insert as many components as you like between the component that provides context and the one that uses it. This includes both built-in components like <div> and components you might build yourself.

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

¥In this example, the same Post component (with a dashed border) is rendered at two different nesting levels. Notice that the <Heading> inside of it gets its level automatically from the closest <Section>:

import Heading from './Heading.js';
import Section from './Section.js';

export default function ProfilePage() {
  return (
    <Section>
      <Heading>My Profile</Heading>
      <Post
        title="Hello traveller!"
        body="Read about my adventures."
      />
      <AllPosts />
    </Section>
  );
}

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

function RecentPosts() {
  return (
    <Section>
      <Heading>Recent Posts</Heading>
      <Post
        title="Flavors of Lisbon"
        body="...those pastéis de nata!"
      />
      <Post
        title="Buenos Aires in the rhythm of tango"
        body="I loved it!"
      />
    </Section>
  );
}

function Post({ title, body }) {
  return (
    <Section isFancy={true}>
      <Heading>
        {title}
      </Heading>
      <p><i>{body}</i></p>
    </Section>
  );
}

你没有为此做任何特别的事情。Section 指定其中树的上下文,因此你可以在任何地方插入 <Heading>,并且它将具有正确的大小。在上面的沙盒中试试吧!

¥You didn’t do anything special for this to work. A Section specifies the context for the tree inside it, so you can insert a <Heading> anywhere, and it will have the correct size. Try it in the sandbox above!

上下文使你可以编写 “适应它们的环境” 的组件,并根据它们被渲染的位置(或者换句话说,在哪个上下文中)以不同方式显示它们自己。

¥Context lets you write components that “adapt to their surroundings” and display themselves differently depending on where (or, in other words, in which context) they are being rendered.

上下文的工作方式可能会让你想起 CSS 属性继承。 在 CSS 中,你可以为 <div> 指定 color: blue,并且其中的任何 DOM 节点,无论多深,都将继承该颜色,除非中间的某个其他 DOM 节点用 color: green 覆盖它。同样,在 React 中,覆盖来自上方的某些上下文的唯一方法是将子级封装到具有不同值的上下文提供器中。

¥How context works might remind you of CSS property inheritance. In CSS, you can specify color: blue for a <div>, and any DOM node inside of it, no matter how deep, will inherit that color unless some other DOM node in the middle overrides it with color: green. Similarly, in React, the only way to override some context coming from above is to wrap children into a context provider with a different value.

在 CSS 中,不同的属性(如 colorbackground-color)不会相互覆盖。你可以将所有 <div>color 设置为红色,而不影响 background-color。同样,不同的 React 上下文不会相互覆盖。你使用 createContext() 创建的每个上下文都与其他上下文完全分开,并使用和提供该特定上下文将组件连接在一起。一个组件可以毫无问题地使用或提供许多不同的上下文。

¥In CSS, different properties like color and background-color don’t override each other. You can set all <div>’s color to red without impacting background-color. Similarly, different React contexts don’t override each other. Each context that you make with createContext() is completely separate from other ones, and ties together components using and providing that particular context. One component may use or provide many different contexts without a problem.

使用上下文之前

¥Before you use context

上下文非常诱人使用!然而,这也意味着它很容易被过度使用。仅仅因为你需要深入传递一些属性并不意味着你应该将这些信息放入上下文中。

¥Context is very tempting to use! However, this also means it’s too easy to overuse it. Just because you need to pass some props several levels deep doesn’t mean you should put that information into context.

在使用上下文之前,你应该考虑以下几个备选方案:

¥Here’s a few alternatives you should consider before using context:

  1. 传递属性 开始 如果你的组件并不简单,那么通过十几个组件传递十几个属性并不罕见。它可能感觉像长途跋涉,但它非常清楚哪些组件使用哪些数据!维护你的代码的人会很高兴你已经使用属性明确了数据流。

    ¥Start by passing props. If your components are not trivial, it’s not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you’ve made the data flow explicit with props.

  2. 提取组件并 将 JSX 作为 children 传递 给它们。如果你通过许多不使用该数据的中间组件层传递某些数据(并且仅将其进一步向下传递),这通常意味着你忘记沿途提取某些组件。例如,也许你将 posts 之类的数据属性传递给不直接使用它们的可视化组件,例如 <Layout posts={posts} />。而是,让 Layoutchildren 作为属性,并渲染 <Layout><Posts posts={posts} /></Layout>。这减少了指定数据的组件和需要数据的组件之间的层数。

    ¥Extract components and pass JSX as children to them. If you pass some data through many layers of intermediate components that don’t use that data (and only pass it further down), this often means that you forgot to extract some components along the way. For example, maybe you pass data props like posts to visual components that don’t use them directly, like <Layout posts={posts} />. Instead, make Layout take children as a prop, and render <Layout><Posts posts={posts} /></Layout>. This reduces the number of layers between the component specifying the data and the one that needs it.

如果这些方法都不适合你,请考虑上下文。

¥If neither of these approaches works well for you, consider context.

上下文用例

¥Use cases for context

  • 主题:如果你的应用允许用户更改其外观(例如夜间模式),你可以将上下文提供器放在应用的顶部,并在需要调整其视觉外观的组件中使用该上下文。

    ¥Theming: If your app lets the user change its appearance (e.g. dark mode), you can put a context provider at the top of your app, and use that context in components that need to adjust their visual look.

  • 当前账户:许多组件可能需要知道当前登录的用户。将它放在上下文中可以方便地在树中的任何位置读取它。某些应用还允许你同时操作多个账户(例如,以不同用户的身份发表评论)。在这些情况下,将 UI 的一部分封装到具有不同当前账户值的嵌套提供器中会很方便。

    ¥Current account: Many components might need to know the currently logged in user. Putting it in context makes it convenient to read it anywhere in the tree. Some apps also let you operate multiple accounts at the same time (e.g. to leave a comment as a different user). In those cases, it can be convenient to wrap a part of the UI into a nested provider with a different current account value.

  • 路由大多数路由解决方案在内部使用上下文来保存当前路由。这就是每个链接 “知道” 是否处于活动状态的方式。如果你构建自己的路由,你可能也想这样做。

    ¥Routing: Most routing solutions use context internally to hold the current route. This is how every link “knows” whether it’s active or not. If you build your own router, you might want to do it too.

  • 管理状态:随着你的应用的增长,你最终可能会在靠近应用顶部的地方看到很多状态。下面的许多远程组件可能想要更改它。将 reducer 与上下文一起使用 经常管理复杂的状态并将其传递给远程组件而没有太多麻烦。

    ¥Managing state: As your app grows, you might end up with a lot of state closer to the top of your app. Many distant components below may want to change it. It is common to use a reducer together with context to manage complex state and pass it down to distant components without too much hassle.

上下文不限于静态值。如果你在下一次渲染时传递一个不同的值,React 将更新下面读取它的所有组件!这就是上下文经常与状态结合使用的原因。

¥Context is not limited to static values. If you pass a different value on the next render, React will update all the components reading it below! This is why context is often used in combination with state.

一般而言,如果树的不同部分中的远距离组件需要某些信息,则这是一个很好的指示,上下文可以帮助你。

¥In general, if some information is needed by distant components in different parts of the tree, it’s a good indication that context will help you.

回顾

  • 上下文让组件向其下方的整个树提供一些信息。

    ¥Context lets a component provide some information to the entire tree below it.

  • 传递上下文:

    ¥To pass context:

    1. 使用 export const MyContext = createContext(defaultValue) 创建并导出它。

      ¥Create and export it with export const MyContext = createContext(defaultValue).

    2. 将它传递给 useContext(MyContext) 钩子以在任何子组件中读取它,无论多深。

      ¥Pass it to the useContext(MyContext) Hook to read it in any child component, no matter how deep.

    3. 将子级封装到 <MyContext.Provider value={...}> 中以从父级那里提供。

      ¥Wrap children into <MyContext.Provider value={...}> to provide it from a parent.

  • 上下文贯穿中间的任何组件。

    ¥Context passes through any components in the middle.

  • 上下文使你可以编写 “适应它们的环境” 的组件。

    ¥Context lets you write components that “adapt to their surroundings”.

  • 在使用上下文之前,尝试传递属性或将 JSX 作为 children 传递。

    ¥Before you use context, try passing props or passing JSX as children.

挑战 1 / 1:
用上下文替换属性钻取

¥Replace prop drilling with context

在此示例中,切换复选框会更改传递给每个 <PlaceImage>imageSize 属性。复选框状态保存在顶层 App 组件中,但每个 <PlaceImage> 都需要知道它。

¥In this example, toggling the checkbox changes the imageSize prop passed to each <PlaceImage>. The checkbox state is held in the top-level App component, but each <PlaceImage> needs to be aware of it.

目前,AppimageSize 传递给 ListList 将其传递给每个 Place,再将其传递给 PlaceImage。删除 imageSize 属性,而是将其从 App 组件直接传递给 PlaceImage

¥Currently, App passes imageSize to List, which passes it to each Place, which passes it to the PlaceImage. Remove the imageSize prop, and instead pass it from the App component directly to PlaceImage.

你可以在 Context.js 中声明上下文。

¥You can declare context in Context.js.

import { useState } from 'react';
import { places } from './data.js';
import { getImageUrl } from './utils.js';

export default function App() {
  const [isLarge, setIsLarge] = useState(false);
  const imageSize = isLarge ? 150 : 100;
  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={isLarge}
          onChange={e => {
            setIsLarge(e.target.checked);
          }}
        />
        Use large images
      </label>
      <hr />
      <List imageSize={imageSize} />
    </>
  )
}

function List({ imageSize }) {
  const listItems = places.map(place =>
    <li key={place.id}>
      <Place
        place={place}
        imageSize={imageSize}
      />
    </li>
  );
  return <ul>{listItems}</ul>;
}

function Place({ place, imageSize }) {
  return (
    <>
      <PlaceImage
        place={place}
        imageSize={imageSize}
      />
      <p>
        <b>{place.name}</b>
        {': ' + place.description}
      </p>
    </>
  );
}

function PlaceImage({ place, imageSize }) {
  return (
    <img
      src={getImageUrl(place)}
      alt={place.name}
      width={imageSize}
      height={imageSize}
    />
  );
}


React 中文网 - 粤ICP备13048890号