组件(Components) 和 属性(Props)

组件(Components) 让你可以将用户界面分成独立的,可复用的小部件,并可以对每个部件进行单独的设计。本页提供了组件概念的介绍。 你可以在这里找到详细的组件API参考

从定义上来说, 组件就像JavaScript的函数。组件可以接收任意输入(称为”props”), 并返回 React 元素,用以描述屏幕显示内容。

愚人码头注:Props , 即属性(Property), 在代码中写作 props , 故可用 props 指代 properties 。

函数式组件和类组件

最简单的定义组件的方法是写一个 JavaScript 函数:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

这个函数是一个有效的 React 组件,因为它接收一个 props 参数, 并返回一个 React 元素。 我们把此类组件称为”函数式(Functional)“组件, 因为从字面上看来它就是一个 JavaScript 函数。

你也可以用一个 ES6 的 class 来定义一个组件:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

上面两个组件从 React 的角度来看是等效的。

类组件有一些额外的特性,我们将在下一节讨论。在此之前, 我们先用函数式组件,因为它们更加简洁。

渲染一个组件

在前面, 我们只遇到代表 DOM 标签的 React 元素:

const element = <div />;

然而,元素也可以代表用户定义的组件:

const element = <Welcome name="Sara" />;

当 React 遇到一个代表用户定义组件的元素时,它将 JSX 属性以一个单独对象的形式传递给相应的组件。 我们将其称为 “props” 对象。

比如, 以下代码在页面上渲染 “Hello, Sara” :

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

Try it on CodePen

我们简单扼要重述一下上面这个例子:

  1. 我们调用了 ReactDOM.render() 方法并向其中传入了 <Welcome name="Sara" /> 元素。
  2. React 调用 Welcome 组件,并向其中传入了 {name: 'Sara'} 作为 props 对象。
  3. Welcome 组件返回 <h1>Hello, Sara</h1>
  4. React DOM 迅速更新 DOM ,使其显示为 <h1>Hello, Sara</h1>

警告:

组件名称总是以大写字母开始。

举例来说, <div /> 代表一个 DOM 标签,而 <Welcome /> 则代表一个组件,并且需要在作用域中有一个 Welcome 组件。

你可以在这里阅读更多关于这个约定背后的推理。

合成组件

组件可以在它们的输出中引用其它组件。这使得我们可以使用同样的组件来抽象到任意层级。一个按钮,一个表单,一个对话框,一个屏幕:在 React 应用中,所有这些都通常描述为组件。

例如,我们可以创建一个 App 组件,并在其内部多次渲染 Welcome

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Try it on CodePen

通常,新的 React apps 都有一个单独的顶层 App 组件。然而,如果你在已有的应用中整合 React,你可以需要由下至上地, 从类似于 Button 这样的小组件开始, 逐渐整合到视图层的顶层。

提取组件

不要害怕把一个组件分为多个更小的组件。

举个例子,思考下名 Comment 组件:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

Try it on CodePen

它接受 author(一个对象),text(一个字符串)和 date(一个日期)作为 props,并用于在某社交网站中描述一条评论。

这个组件修改起来很麻烦,因为它是被嵌套的,而且很难复用其中的某个部分。让我们从其中提取一些组件。

首先,提取头像 Avatar

function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

Avatar 组件不用关心它在 Comment 中是如何渲染的。这是为什么我们它的 prop 一个更通用的属性名: user, 而不是 author 的原因。

我们建议从组件本身的角度来命名 props 而不是它被使用的上下文环境。

我们可以稍微简化一下 Comment 组件:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

接下来,我们提取用户信息 UserInfo 组件, 用于将 Avatar 显示在用户名旁边:

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

这使我们可以进一步简化 Comment 组件:

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

Try it on CodePen

提取组件可能看起来是一个繁琐的工作,但是在大型的 Apps 中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 (ButtonPanelAvatar),或者本身足够复杂(AppFeedStoryComment),最好的做法是使其成为可复用组件。

Props 是只读的

无论你用函数或类的方法来声明组件, 它都无法修改其自身 props. 思考下列 sum (求和)函数:

function sum(a, b) {
  return a + b;
}

这种函数称为 “纯函数” ,因为它们不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果。

反之, 以下是非纯函数, 因为它改变了自身的输入值:

function withdraw(account, amount) {
  account.total -= amount;
}

虽然 React 很灵活,但是它有一条严格的规则:

所有 React 组件都必须是纯函数,并禁止修改其自身 props 。

当然, 应用 UI 总是动态的,并且随时有可以改变。 所以在下一节, 我们会介绍一个新的概念 state(状态) 。state 允许 React 组件在不违反上述规则的情况下, 根据用户操作, 网络响应, 或者其他随便什么东西, 来动态地改变其输出。