React 组件

React 组件

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。本指南旨在介绍组件的相关理念。

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

一个 React 应用就是构建在 React 组件之上的。

组件有两个核心概念:

  • props

  • state

一个组件就是通过这两个属性的值在 render 方法里面生成这个组件对应的 HTML 结构。

注意:组件生成的 HTML 结构只能有一个单一的根节点。

函数组件与 class 组件

定义组件最简单的方式就是编写 JavaScript 函数:

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

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

你同时还可以使用 ES6 的 class 来定义组件:

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

上述两个组件在 React 里是等效的。

渲染组件

之前,我们遇到的 React 元素都只是 DOM 标签:

const element = <div />;

不过,React 元素也可以是用户自定义的组件:

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

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为 “props”。

例如,这段代码会在页面上渲染 “Hello, Sara”:

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

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

让我们来回顾一下这个例子中发生了什么:

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

注意: 组件名称必须以大写字母开头。

React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome。

你可以在深入 JSX中了解更多关于此规范的原因。

划分状态数据

一条原则:让组件尽可能地少状态。

这样组件逻辑就越容易维护。

什么样的数据属性可以当作状态?

当更改这个状态(数据)需要更新组件 UI 的就可以认为是 state,下面这些可以认为不是状态:

  • 可计算的数据:比如一个数组的长度

  • 和 props 重复的数据:除非这个数据是要做变更的

最后回过头来反复看几遍 Thinking in React,相信会对组件有更深刻的认识。

无状态组件

你也可以用纯粹的函数来定义无状态的组件(stateless function),这种组件没有状态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构。无状态组件非常简单,开销很低,如果可能的话尽量使用无状态组件。比如使用箭头函数定义:

const HelloMessage = (props) => <div> Hello {props.name}</div>;
render(<HelloMessage name="John" />, mountNode);

因为无状态组件只是函数,所以它没有实例返回,这点在想用 refs 获取无状态组件的时候要注意,参见DOM 操作

组件生命周期

一般来说,一个组件类由 extends Component 创建,并且提供一个 render 方法以及其他可选的生命周期函数、组件相关的事件或方法来定义。

一个简单的例子:

import React, { Component } from 'react';
import { render } from 'react-dom';

class LikeButton extends Component {
  constructor(props) {
    super(props);
    this.state = { liked: false };
  }

  handleClick(e) {
    this.setState({ liked: !this.state.liked });
  }

  render() {
    const text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick.bind(this)}>
          You {text} this. Click to toggle.
      </p>
    );
  }
}

render(
    <LikeButton />,
    document.getElementById('example')
);

getInitialState

初始化 this.state 的值,只在组件装载之前调用一次。

如果是使用 ES6 的语法,你也可以在构造函数中初始化状态,比如:

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: props.initialCount };
  }

  render() {
    // ...
  }
}

getDefaultProps

只在组件创建时调用一次并缓存返回的对象(即在 React.createClass 之后就会调用)。

因为这个方法在实例初始化之前调用,所以在这个方法里面不能依赖 this 获取到这个组件的实例。

在组件装载之后,这个方法缓存的结果会用来保证访问 this.props 的属性时,当这个属性没有在父组件中传入(在这个组件的 JSX 属性里设置),也总是有值的。

如果是使用 ES6 语法,可以直接定义 defaultProps 这个类属性来替代,这样能更直观的知道 default props 是预先定义好的对象值:

Counter.defaultProps = { initialCount: 0 };

render

必须组装生成这个组件的 HTML 结构(使用原生 HTML 标签或者子组件),也可以返回 null 或者 false,这时候 ReactDOM.findDOMNode(this) 会返回 null。

生命周期函数

装载组件触发

componentWillMount

只会在装载之前调用一次,在 render 之前调用,你可以在这个方法里面调用 setState 改变状态,并且不会导致额外调用一次 render

componentDidMount

只会在装载完成之后调用一次,在 render 之后调用,从这里开始可以通过 ReactDOM.findDOMNode(this)获取到组件的 DOM 节点。

更新组件触发

这些方法不会在首次 render 组件的周期调用

  • componentWillReceiveProps

  • shouldComponentUpdate

  • componentWillUpdate

  • componentDidUpdate

卸载组件触发

  • componentWillUnmount

更多关于组件相关的方法说明,参见: