「译」向 React 组件传递属性

398 阅读4分钟

本文译自官方文档:Passing Props to a Component

React 组件通过属性(props)实现相互通信。每个父组件可以向其子组件的 props 传递一些信息。props 可能会让你联想到 HTML 的属性(attributes),但是你可以向 props 传递任意数据类型,包括对象、数组和函数。

你会学到

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

常见的 props

props 是向 JSX 标签传递的信息。比如 className, src, alt, width, height 是一些 <img> 支持的 props:

function Avatar() {
    return (
        <img 
            className="avatar"
            src="xxx.jpg"
            alt="Lin Lanying"
            width={100}
            height={100}
        />
    );
}

可以向 <img> 标签传递的 props 是预定义的(ReactDOM 遵循 HTML 标准)。但是,你可以向自定义组件(比如 <Avatar />)传递任意属性。

向组件传递 props

以自定义组件 Avatar 为例,向它传递 props 分两步走

第一步:向子组件传递 props

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

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

此时,可以在 Avatar 组件内部读取这些 props。

第二步:在子组件内部读取 props

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

指定 props 的默认值

使用解构语法的 = 可以设定参数默认值:

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

使用 JSX 扩展语法传递 props

有时候,传递 props 时非常重复:

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

重复性代码本身没错 --- 它的意图更明确。但有时也许你更偏爱简洁。一些组件会把全部 props 转发给子组件,就像上面 ProfileAvatar 做的那样。因为它自身不会直接使用 props,使用更紧凑的“扩展”语法也是合理的:

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

使用扩展语法时要克制。如果你几乎每个组件都使用,很可能用错了方法。通常,这意味着你需要拆分组件,把子组件当作 JSX 传递。后文会详解。

将 JSX 当作 children 传递

在浏览器原生标签中经常使用嵌套用法:

<div>
    <img />
</div>

有时候,你也期望可以像这样嵌套自己的组件:

<Card>
    <Avatar />
</Card>

当你在 JSX 标签中嵌套内容时,父组件接收的内容,将放在一个名叫 children 的 prop 中。比如,下面的 Card 组件接收的 children prop 将设为 <Avatar /> 并将它包裹在 div 中渲染:

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

试着将 <Card> 内部的 <Avatar> 替换为一些文本,可以看到 Card 组件可以包裹任意类型的内容。它无需在意内部渲染的元素。许多场景可以看到这种灵活模式。

自带 children prop 的组件可以想象成有一个“洞”,它的父组件可以使用 JSX 填充这个洞。children prop 组件通常用作视觉包裹容器:比如面板、表格等等。可以在提取布局元素中看到更多应用。

i_children-prop.png

props 如何随时间变化

下面的 Clock 组件从父组件接收两个 props:colortime。(暂时忽略父组件的代码,因为它使用了 state,先不深究 state 的用法)。

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

这个例子展示了组件可能在不同时刻接收不同的 props。props 不是静止的!这里的 time prop 每秒变化一次,color prop 随着下拉菜单的变更而变化。props 反映了任意时刻组件的数据,并非仅仅初始值。

但是,props 是不可变的 --- 这是一个计算机科学的术语,表示“不能更改”。当组件需要改变自己的 props(比如,为了应对用户交互或新的数据),它需要告知父组件传递不同的 props --- 一个新的对象!旧 props 会搁置一旁,JS 引擎最终回收旧 props 占用的内存空间。

总结

  • 传递 props 时,添加到 JSX 上即可,就像 HTML 属性一样。
  • 读取 props 需要使用 function Avatar({ person, size }) 解构语法。
  • 可以指定默认值,比如 size = 100
  • 可以使用 JSX 扩展语法 <Avatar {...props} /> 转发所有的 props,但不要过量!
  • 嵌套的 JSX <Card><Avatar /></Card> 会出现在 Card 组件的 children prop。
  • props 是只读的时间快照:每次渲染都会拥有新的 props。
  • 你不能改变 props。当需要交互时,需要设定 state。