向组件传递 Props

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

你将学习

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

熟悉的 props

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

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> 标签的 props 是预定义的(ReactDOM 符合 HTML 标准)。但是,你可以向你自己的组件(例如 <Avatar>)传递任何 props,以对其进行自定义。方法如下!

向组件传递 props

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

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

你可以通过两个步骤向 Avatar 传递一些 props。

步骤 1:向子组件传递 props

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

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

注意

如果 person= 后的双花括号让你感到困惑,请回想一下 它们只是 JSX 花括号内的对象

现在,你可以在 Avatar 组件中读取这些 props。

步骤 2:在子组件中读取 props

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

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

Avatar 添加一些逻辑,使其使用 personsize 属性进行渲染,就大功告成了。

现在,您可以使用不同的属性配置 Avatar 以多种不同的方式呈现。 试试调整这些值!

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>
  );
}

属性可以让您独立地考虑父组件和子组件。 例如,您可以更改 Profile 内部的 personsize 属性,而无需考虑 Avatar 如何使用它们。 同样,您可以更改 Avatar 使用这些属性的方式,而无需查看 Profile

您可以将属性视为可以调整的“旋钮”。 它们的作用与函数的参数相同,实际上,属性 *是* 组件的唯一参数!React 组件函数接受单个参数,即 props 对象。

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

通常,您不需要整个 props 对象本身,因此您可以将其解构为单个属性。

陷阱

在声明属性时,不要遗漏 {} 花括号() 内部。

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

这种语法被称为 “解构”,相当于从函数参数中读取属性。

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

为属性指定默认值

如果要在未指定值时为属性提供默认值,您可以通过在参数后立即放置 = 和默认值来使用解构来实现。

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

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

仅当缺少 size 属性或传递 size={undefined} 时,才会使用默认值。 但是,如果传递 size={null}size={0},则 不会 使用默认值。

使用 JSX 展开语法转发属性

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

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

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

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

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

谨慎使用展开语法。 如果您在其他所有组件中都使用它,则说明存在问题。 通常,这表明您应该拆分子组件并将子组件作为 JSX 传递。 下面将详细介绍!

将 JSX 作为子组件传递

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

<div>
<img />
</div>

有时,您需要以相同的方式嵌套您自己的组件。

<Card>
<Avatar />
</Card>

当您将内容嵌套在 JSX 标签内时,父组件将在名为 children 的属性中接收该内容。 例如,下面的 Card 组件将接收一个设置为 <Avatar />children 属性,并将其渲染在包装器 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 组件如何包装任何嵌套内容。 它不需要“知道”它内部正在渲染什么。 您将在许多地方看到这种灵活的模式。

您可以将具有 children 属性的组件视为具有一个“孔”,其父组件可以使用任意 JSX“填充”该孔。 您通常会将 children 属性用于可视化包装器:面板、网格等。

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

插图作者 Rachel Lee Nabors

属性如何随时间变化

下面的 Clock 组件从其父组件接收两个 prop:colortime。(父组件的代码被省略了,因为它使用了状态,我们现在先不深入讨论。)

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

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

这个例子说明了一个组件可能会随着时间的推移接收到不同的 prop。Prop 并不总是静态的!在这里,time prop 每秒都会改变,而 color prop 会在您选择另一种颜色时改变。Prop 反映了组件在任何时间点的数据,而不仅仅是在开始时。

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

不要试图“更改 prop”。 当您需要响应用户输入时(例如更改所选颜色),您需要“设置状态”,您可以在状态:组件的内存中了解到。

总结

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

挑战 1 3:
提取组件

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

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>
  );
}