将属性传递给组件

React 组件使用 props 来进行相互通信。每个父组件都可以通过向子组件传递 props 来传递一些信息。Props 可能会让你联想到 HTML 属性,但你可以通过它们传递任何 JavaScript 值,包括对象、数组和函数。

🌐 React components use props to communicate with each other. Every parent component can pass some information to its child components by giving them props. Props might remind you of HTML attributes, but you can pass any JavaScript value through them, including objects, arrays, and functions.

你将学习到

  • 如何将属性传递给组件
  • 如何从组件中读取属性
  • 如何为属性指定默认值
  • 如何将一些 JSX 传递给组件
  • 属性如何随时间变化

熟悉的属性

🌐 Familiar props

Props 是你传递给 JSX 标签的信息。例如,classNamesrcaltwidthheight 是你可以传递给 <img> 的一些 props:

🌐 Props are the information that you pass to a JSX tag. For example, className, src, alt, width, and height are some of the props you can pass to an <img>:

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Lin Lanying"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return (
    <Avatar />
  );
}

你可以传递给 <img> 标签的属性是预定义的(ReactDOM 遵循 HTML 标准)。但你可以向你自己的组件传递任何属性,例如 <Avatar>,以自定义它们。方法如下!

🌐 The props you can pass to an <img> tag are predefined (ReactDOM conforms to the HTML standard). But you can pass any props to your own components, such as <Avatar>, to customize them. Here’s how!

将属性传递给组件

🌐 Passing props to a component

在这段代码中,Profile 组件没有向其子组件 Avatar 传递任何 props:

🌐 In this code, the Profile component isn’t passing any props to its child component, Avatar:

export default function Profile() {
return (
<Avatar />
);
}

你可以通过两个步骤给 Avatar 一些赞扬。

🌐 You can give Avatar some props in two steps.

步骤 1:向子组件传递属性

🌐 Step 1: Pass props to the child component

首先,向 Avatar 传递一些属性。例如,我们传递两个属性:person(一个对象)和 size(一个数字):

🌐 First, pass some props to Avatar. For example, let’s pass two props: person (an object), and size (a number):

export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

注意

如果 person= 后的双大括号让你困惑,记住 它们只是 JSX 大括号内的一个对象

🌐 If double curly braces after person= confuse you, recall they’re merely an object inside the JSX curlies.

现在你可以在 Avatar 组件内部读取这些属性。

🌐 Now you can read these props inside the Avatar component.

步骤 2:读取子组件内部的props

🌐 Step 2: Read props inside the child component

你可以通过列出它们的名称 person, size,用逗号分隔,并直接放在 function Avatar 之后的 ({}) 中来读取这些属性。这让你可以像使用变量一样在 Avatar 代码中使用它们。

🌐 You can read these props by listing their names person, size separated by the commas inside ({ and }) directly after function Avatar. This lets you use them inside the Avatar code, like you would with a variable.

function Avatar({ person, size }) {
// person and size are available here
}

Avatar 中添加一些逻辑,使用 personsize 属性进行渲染,就完成了。

🌐 Add some logic to Avatar that uses the person and size props for rendering, and you’re done.

现在你可以配置 Avatar 使用不同的属性以多种方式渲染。试着调整这些值吧!

🌐 Now you can configure Avatar to render in many different ways with different props. Try tweaking the values!

import { getImageUrl } from './utils.js';

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi', 
          imageId: 'YfeOqp2'
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'Aklilu Lemma', 
          imageId: 'OKS67lh'
        }}
      />
      <Avatar
        size={50}
        person={{ 
          name: 'Lin Lanying',
          imageId: '1bX5QH6'
        }}
      />
    </div>
  );
}

Props 让你可以独立地思考父组件和子组件。例如,你可以在 Profile 内更改 personsize 的 props,而不必考虑 Avatar 如何使用它们。同样,你也可以更改 Avatar 如何使用这些 props,而无需查看 Profile

🌐 Props let you think about parent and child components independently. For example, you can change the person or the size props inside Profile without having to think about how Avatar uses them. Similarly, you can change how the Avatar uses these props, without looking at the Profile.

你可以把 props 想象成可以调整的“旋钮”。它们的作用与函数的参数相同——实际上,props 就是你组件的唯一参数!React 组件函数接受一个参数,一个 props 对象:

🌐 You can think of props like “knobs” that you can adjust. They serve the same role as arguments serve for functions—in fact, props are the only argument to your component! React component functions accept a single argument, a props object:

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

通常你不需要整个 props 对象本身,所以你将它解构为各个属性。

🌐 Usually you don’t need the whole props object itself, so you destructure it into individual props.

易犯错误

在声明 props 时,不要忘记在 () 内使用 {} 大括号的组合

function Avatar({ person, size }) {
// ...
}

这种语法称为”解构”,等同于从函数参数中读取属性:

🌐 This syntax is called “destructuring” and is equivalent to reading properties from a function parameter:

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

为属性指定默认值

🌐 Specifying a default value for a prop

如果你想为一个 prop 提供一个默认值以在未指定值时使用,你可以通过解构来实现,在参数后面直接写上 = 和默认值:

🌐 If you want to give a prop a default value to fall back on when no value is specified, you can do it with the destructuring by putting = and the default value right after the parameter:

function Avatar({ person, size = 100 }) {
// ...
}

现在,如果 <Avatar person={...} /> 在没有 size 属性的情况下渲染,size 将被设置为 100

🌐 Now, if <Avatar person={...} /> is rendered with no size prop, the size will be set to 100.

默认值仅在缺少 size 属性或传入 size={undefined} 时使用。但如果你传入 size={null}size={0},默认值将 不会 被使用。

🌐 The default value is only used if the size prop is missing or if you pass size={undefined}. But if you pass size={null} or size={0}, the default value will not be used.

使用 JSX 展开语法转发属性

🌐 Forwarding props with the JSX spread syntax

有时,传递属性会变得非常重复:

🌐 Sometimes, passing props gets very repetitive:

function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}

重复的代码没有错——它可能更易读。但有时你可能更看重简洁性。一些组件会将它们的所有属性传递给子组件,就像这个 ProfileAvatar 所做的那样。因为它们不直接使用任何属性,所以使用更简洁的“展开”语法是有意义的:

🌐 There’s nothing wrong with repetitive code—it can be more legible. But at times you may value conciseness. Some components forward all of their props to their children, like how this Profile does with Avatar. Because they don’t use any of their props directly, it can make sense to use a more concise “spread” syntax:

function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

这会将 Profile 的所有属性转发给 Avatar,而无需列出它们各自的名称。

🌐 This forwards all of Profile’s props to the Avatar without listing each of their names.

谨慎使用展开语法。 如果你在每个组件中都使用它,说明可能有问题。通常,这表明你应该拆分组件并以 JSX 形式传递子元素。下一节会详细讲解!

作为子级传递 JSX

🌐 Passing JSX as children

嵌套内置浏览器标签很常见:

🌐 It is common to nest built-in browser tags:

<div>
<img />
</div>

有时你会希望以相同的方式嵌套自己的组件:

🌐 Sometimes you’ll want to nest your own components the same way:

<Card>
<Avatar />
</Card>

当你在 JSX 标签内嵌套内容时,父组件将会在一个名为 children 的 prop 中接收到该内容。例如,下面的 Card 组件将会接收到一个 children prop,其值为 <Avatar />,并在一个封装的 div 中渲染它:

🌐 When you nest content inside a JSX tag, the parent component will receive that content in a prop called children. For example, the Card component below will receive a children prop set to <Avatar /> and render it in a wrapper div:

import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

尝试将 <Card> 内的 <Avatar> 替换为一些文本,以查看 Card 组件如何封装任何嵌套内容。它不需要“知道”里面渲染了什么。你会在很多地方看到这种灵活的模式。

🌐 Try replacing the <Avatar> inside <Card> with some text to see how the Card component can wrap any nested content. It doesn’t need to “know” what’s being rendered inside of it. You will see this flexible pattern in many places.

你可以把拥有 children 属性的组件看作有一个“空洞”,可以被其父组件用任意 JSX“填充”。你经常会使用 children 属性来做视觉封装:面板、网格等。

🌐 You can think of a component with a children prop as having a “hole” that can be “filled in” by its parent components with arbitrary JSX. You will often use the children prop for visual wrappers: panels, grids, etc.

A puzzle-like Card tile with a slot for "children" pieces like text and Avatar

Illustrated by Rachel Lee Nabors

属性如何随时间变化

🌐 How props change over time

下面的 Clock 组件从它的父组件接收两个 props:colortime。(父组件的代码被省略,因为它使用了 state,我们暂时不会深入讨论。)

🌐 The Clock component below receives two props from its parent component: color and time. (The parent component’s code is omitted because it uses state, which we won’t dive into just yet.)

尝试在下面的选择框中更改颜色:

🌐 Try changing the color in the select box below:

export default function Clock({ color, time }) {
  return (
    <h1 style={{ color: color }}>
      {time}
    </h1>
  );
}

这个例子说明了**一个组件可能会随着时间接收不同的 props。**Props 并不总是静态的!在这里,time prop 每秒都会变化,而 color prop 会在你选择其他颜色时变化。Props 反映了组件在任何时间点的数据,而不仅仅是在一开始。

🌐 This example illustrates that a component may receive different props over time. Props are not always static! Here, the time prop changes every second, and the color prop changes when you select another color. Props reflect a component’s data at any point in time, rather than only in the beginning.

然而,props 是不可变的—这是计算机科学中的一个术语,意思是“不可更改”。当一个组件需要更改它的 props(例如,响应用户交互或新数据时),它必须“请求”其父组件传递给它_不同的 props_——一个新的对象!它的旧 props 将被丢弃,最终 JavaScript 引擎会回收它们占用的内存。

🌐 However, props are immutable—a term from computer science meaning “unchangeable”. When a component needs to change its props (for example, in response to a user interaction or new data), it will have to “ask” its parent component to pass it different props—a new object! Its old props will then be cast aside, and eventually the JavaScript engine will reclaim the memory taken by them.

不要尝试“更改 props”。 当你需要响应用户输入(例如更改所选颜色)时,你需要“设置状态”,你可以在 State: A Component’s Memory. 中了解相关内容。

回顾

  • 要传递属性,将它们添加到 JSX,就像使用 HTML 属性一样。
  • 要读取 props,使用 function Avatar({ person, size }) 解构语法。
  • 你可以指定一个默认值,例如 size = 100,它用于缺失的和 undefined 属性。
  • 你可以使用 <Avatar {...props} /> JSX 展开语法传递所有属性,但不要过度使用它!
  • 嵌套的 JSX,例如 <Card><Avatar /></Card>,将出现在 Card 组件的 children 属性中。
  • Props 是只读的时间快照:每次渲染都会收到 props 的新版本。
  • 你不能更改 props。当你需要交互性时,你需要设置 state。

挑战 1 of 3:
提取一个组件

🌐 Extract a component

Gallery 组件包含两个配置文件的一些非常相似的标记。将其提取出 Profile 组件以减少重复。你需要选择要传递给它的 props。

🌐 This Gallery component contains some very similar markup for two profiles. Extract a Profile component out of it to reduce the duplication. You’ll need to choose what props to pass to it.

import { getImageUrl } from './utils.js';

export default function Gallery() {
  return (
    <div>
      <h1>Notable Scientists</h1>
      <section className="profile">
        <h2>Maria Skłodowska-Curie</h2>
        <img
          className="avatar"
          src={getImageUrl('szV5sdG')}
          alt="Maria Skłodowska-Curie"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            physicist and chemist
          </li>
          <li>
            <b>Awards: 4 </b> 
            (Nobel Prize in Physics, Nobel Prize in Chemistry, Davy Medal, Matteucci Medal)
          </li>
          <li>
            <b>Discovered: </b>
            polonium (chemical element)
          </li>
        </ul>
      </section>
      <section className="profile">
        <h2>Katsuko Saruhashi</h2>
        <img
          className="avatar"
          src={getImageUrl('YfeOqp2')}
          alt="Katsuko Saruhashi"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            geochemist
          </li>
          <li>
            <b>Awards: 2 </b> 
            (Miyake Prize for geochemistry, Tanaka Prize)
          </li>
          <li>
            <b>Discovered: </b>
            a method for measuring carbon dioxide in seawater
          </li>
        </ul>
      </section>
    </div>
  );
}