7

二十二、React.Component用法

组件(Components)允许您将UI拆分为独立的可重用的部分,并单独的考虑每个部分。

总览

React.Component是一个抽象基类。这意味着直接引用React.Component是毫无意义的。你可以实现一个它的子类,并且至少定义一个render()方法即可使用。

你可以使用ES6中class定义一个React组件:

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

如果你还没有使用ES6,你可以使用React.createClass()

组件的生命周期方法

每个组件都有几个『生命周期方法』,您可以重写这些方法,已便在React执行过程中的指定时间运行自己的代码。前缀为Will的生命周期方法会在在一些事情发生之前被调用,带有Did前缀的方法在某些事情发生之后被调用。

Mounting(加载组件)

当创建组件的实例并将其插入DOM时,会依次调用这些方法:

  • constructor()

  • componentWillMount()

  • render()

  • componentDidMount()

Updating(更新状态)

更新可以由prop或者state的改变引起。在重新渲染组件时依次调用这些方法:

  • componentWillReceiveProps()

  • shouldComponentUpdate()

  • componentWillUpdate()

  • render()

  • componentDidUpdate()

Unmounting(卸载组件)

当从DOM中删除组件时,将调用此方法:

  • componentWillUnmount()

其他API

每个组件还提供了一些其他API:

  • setState()

  • forceUpdate()

组件属性

  • defaultProps

  • displayName

  • propTypes

实例内部属性

  • props

  • state


使用方法

render()

render() {
    // return React Elements
}

注意:render()方法是必须写的。

当这个方法被调用时,它会检测this.propsthis.state并返回一个React元素。此元素可以是本地DOM组件的形式,例如<div/>,也可以是您自己定义的一个复合组件。

你也可以返回nullfalse来表示你不想做任何渲染操作。当返回nullfalse时,ReactDOM.findDOMNode(this)将返回null

render()方法应该是纯的(pure function,见函数式编程),这意味着它并不会修改组件state,每次调用它时都会返回相同的结果,它不会直接与浏览器交互。如果您需要与浏览器直接交互,请改用componentDidMount()方法或者其他生命周期方法来执行你的逻辑。保持render()可以让组件更容易去思考自己应该做什么。

提示
如果shouldComponentUpdate()返回false,那么render()不会被执行。


constructor()

constructor(props)

在装载组件(mounting)之前调用会React组件的构造函数。当实现React.Component子类的构造函数时,应该在任何其他语句之前调用super(props)。否则,this.props将在构造函数中未定义,这可能导致错误。

构造函数是初始化state的标准位置。如果不初始化state,并且不绑定组件内部方法的this指向,则不需要为React组件实现构造函数。

如果你知道你在做什么的话,你可以根据props来初始化state。这里有一个有效的React.Component子类构造函数的例子:

constructor(props) {
    super(props);
    this.state = {
        color: props.initialColor
    };
}

注意这种模式,因为它会将props复制一份在state中,这就可能导致一个意外的bug。所以不应该将props复制到state中。相反,你需要使用提升state的技巧,该技巧我们在前面的文章提到过。

如果你使用props复制到state中,你还需要实现componentWillReceiveProps(nextProps)来保持state是最新的。这个时候使用提升state的方法反而会更容易,也能产生更少的bug。


componentWillMount()

componentWillMount()

componentWillMount()是在装载(mounting)发生之前被调用。它在render()之前调用,所以在此方法中的设置state不会造成重新渲染。另外,应该避免在此方法中引入有任何副作用的东西(见函数式编程)

在服务器渲染上这是唯一一个调用的生命周期钩子函数。一般来说,我们建议使用constructor()


componentDidMount()

componentDidMount()

componentDidMount()在组件装载到DOM后立即调用。如果需要进行DOM节点的初始化则应该在这里来执行该逻辑。如果需要从远程端点加载数据(ajax),那么这是处理网络请求的最好地方。在此方法中设置state会去重新渲染DOM。


componentWillReceiveProps()

componentWillReceiveProps(nextProps)

componentWillReceiveProps()在安装好的组件接收新props之前被调用。 如果你需要更新state用来响应props的更改(例如,重置它),你可以在此方法中比较this.propsnextProps并使用this.setState()来替换并重置state。

注意,即使props没有改变,React也可以调用这个方法,因此如果你只想处理props改变的情况,请确保比较当前值和下一个值是否不同。 当父组件引起你的组件重新渲染时,就有可能会发生这种情况。

如果你只是调用this.setState(),那么componentWillReceiveProps()不会被调用。


shouldComponentUpdate()

shouldComponentUpdate(nextProps, netState)

使用shouldComponentUpdate()让React知道组件是否受当前state或props变化的影响。 默认行为是在每次state更改时都会去重新渲染DOM,在绝大多数情况下,你应该依赖于这个默认行为。

当接收到新的props或state时,shouldComponentUpdate()在渲染之前被调用。 默认为true对于初始渲染或使用forceUpdate(),不调用此方法。

返回false不会阻止子组件在state更改时重新渲染。

目前,如果shouldComponentUpdate()返回false,那么将不会调用componentWillUpdate()render()componentDidUpdate()。 注意,在将来React可以将shouldComponentUpdate()作为提示而不是严格的操作指令,返回false仍然可能导致组件的重新渲染。

如果你确定某些组件在某些操作时有点缓慢,你可以让它继承React.PureComponent,而不是继承React.Component

React.PureComponent实现了propsstate进行浅比较的shouldComponentUpdate()方法。 如果你确定想人肉处理这个浅比较操作,你可以自己在这个函数中比较this.propsnextPropsthis.statenextState是否相同。相同返回false,不同返回true,那么React就会根据返回值来确认是否跳过本次DOM渲染。


componentWilUpdate()

componentWillUpdate(nextProps, nextState)

当组件在收到新的props或state时,componentWillUpdate()在渲染之前会立即调用这个方法。 使用这个方法来判断是非需要重新渲染DOM。 第一次渲染DOM不调用此方法。

注意,this.setState()不会调用此方法。 如果你需要根据state和props来进行重新渲染DOM,请改用componentWillReceiveProps()

note
如果shouldComponentUpdate()返回false,则不会调用componentWillUpdate()


componentDidUpdate()

componentDidUpdate(prevProps, prevState)

componentDidUpdate()在重新渲染DOM之后被调用。 第一次渲染不调用此方法。

当组件已经重新渲染后,此方法是一个执行DOM操作的好机会,同时也是一个处理网络请求的好地方,前提是你需要比较当前props与之前的props是否相同(例如,如果props没有改变,那么可能不需要进行网络请求)。

note
如果shouldComponentUpdate()返回false,则不会调用componentDidUpdate()


componentWillUnmount()

componentWillUnmount()

componentWillUnmount()在组件被卸载和销毁之前立即被调用。 此方法可以执行任何有必要的清理工作,例如清理计时器,取消网络请求或清理在componentDidMount()中创建的DOM元素。


setState()

setState(nextState, callback)

将nextState和当前state进行浅合并。 这是用于从事件处理函数和服务器请求回调中触发UI重新渲染的主要方法。

第一个参数可以是一个对象(包含一个或多个要更新的state属性),也可以是返回将要引起重新渲染的对象(state和props)。

这里有一个简单的用法:

this.setState({myKey: 'my new value'});

它也可以传入一个带有参数的函数function(state,props)=> newState。 例如,假设我们想要当前state中的myInteger加上props.step:

this.setState(prevState, props) => {
    return {myInteger: prevState.myInteger + props.step};
}

第二种参数是回调函数,一旦setState完成并且组件被重新渲染,它就会被执行。 通常我们建议使用componentDidUpdate()代替这样的逻辑。

setState()不会立即改变this.state,但会创建并挂起state的修改。 所以在调用此方法中访问this.state可能会返回现有值。

不能保证对setState的所以调用都是同步操作,因为这样做是为了将多次state修改合并为一次以便提高性能。

setState()总会重新渲染DOM,除非shouldComponentUpdate()回false。 如果你正在使用可突变对象,并且无法在shouldComponentUpdate()实现条件渲染逻辑,则只有当新state与先前state不同时调用setState()才能避免不必要的重新渲染。


forceUpdate()

component.forceUpdate(callback)

默认情况下,当组件的state或props改变时,组件将重新渲染。 如果你的render()方法依赖于一些其他数据,你可以告诉React组件需要通过调用forceUpdate()来重新渲染。

调用forceUpdate()会导致在组件上调用render(),跳过shouldComponentUpdate() 这将触发子组件的正常生命周期方法,包括每个子组件的shouldComponentUpdate()方法。 如果标记更改,React仍将更新DOM。

通常你应该尽量避免forceUpdate()的所有使用,并且只能从render()中的this.propsthis.state中读取。


类属性

defaultProps

defaultProps是类组件本身的属性,用来设置类组件的默认props。 可以用来给未传入值的props设置默认值。 例如:

class CustomButton extends React.Component {
    // ...
}
CustomButton.defaultProps = {
    color: 'blue';
}

如果props.color没有定义,就是将它设置为默认值blue

render() {
    return <CustomButton />; // props.color will be set to blue
}

如果props.color被设置为null,那么它将会被重新赋值为null:

render() {
    return <CustomButton color={null} />; // props.color will remain null
}

displayName

displayName字符串用于调试消息。 JSX自动设置此值;


propTypes

propTypes也是类组件本身上的一个属性,用来规范props应该是什么类型。 它是从props的名称到React.PropTypes中定义的类型的映射。 在开发模式下,当为prop设置一个不是指定格式的无效值时,会在JavaScript控制台中显示警告信息。 在生产模式下,为了提高效率,不会进行propTypes检查。

例如,此代码确保颜色prop是一个字符串:

class CustomButton extends React.Component {
    // ...
}
CustomButton.propTypes = {
   color: React.PropTypes.string
};

我们建议尽可能使用Flow,以便在编译时进行类型检查,而不是在运行时进行类型检查。 Flow在React中内置支持,因此可以轻松地在React应用程序上运行静态分析。

// @flow
function foo(x) {
  return x * 10;
}
foo('Hello, world!');

// @flow
function bar(x): string {
  return x.length;
}
bar('Hello, world!');

实例属性

props

this.props包含由此组件的调用者定义的props

特别地,this.props.children是一个特殊的props,通常由JSX表达式中的子标签而不是标签本身定义。

state

state包含特定于此组件的数据,可能随时间更改。 state是用户定义的,它应该是纯JavaScript对象。

如果你不在render()中使用它,它不应该设置state。 例如,您可以将定时器ID直接放在实例上。

永远不要直接改变this.state,因为调用setState()之后可以替换你所做的各种变化, 通常应该把this.state看作是不可变的。


张亚涛
5.3k 声望2.8k 粉丝

人首先应该接受现实,承认问题的存在,并且反思它。而不是首先就跳出来回避这个问题,找比我们更差的。质疑甚至抨击提出问题的人,并且一杆子打死。