【译】React 组件的生命周期

原文:https://medium.com/react-ecosystem/react-components-lifecycle-ce09239010df#.j7h6w8cccreact

译者序:React组件生命周期有不少文章介绍了,这篇做者列出了不少开发中可能不会注意的细节,好比哪些阶段执行setState是否会致使render等,对React组件性能优化有必定的帮助,故译之,不当之处敬请指正!ios

github issue: https://github.com/chemdemo/c...git

一段探索React自建内部构造的旅程

在先前的文章里咱们涵盖了React基本原理如何构建更加复杂的交互组件。此篇文章咱们将会继续探索React组件的特性,特别是生命周期。github

稍微思考一下React组件所作的事,首先想到的是一点是:React描述了如何去渲染(DOM)。咱们已经知道React使用render()方法来达到这个目的。然而仅有render()方法可能不必定都能知足咱们的需求。若是在组件rendered以前或以后咱们须要作些额外的事情该怎么作呢?咱们须要作些什么以免重复渲染(re-render)呢?axios

看起来咱们须要对组件(运行)的各个阶段进行控制,组件运行全部涉及的各个阶段叫作组件的生命周期,而且每个React组件都会经历这些阶段。React提供了一些方法并在组件处于相应的阶段时通知咱们。这些方法叫作React组件的生命周期方法且会根据特定并可预测的顺序被调用。浏览器

基本上全部的React组件的生命周期方法均可以被分割成四个阶段:初始化挂载阶段(mounting)更新阶段卸载阶段(unmounting)。让咱们来近距离分别研究下各个阶段。缓存

初始化阶段

初始化阶段就是咱们分别经过getDefaultProps()getInitialState()方法定义this.props默认值和this.state初始值的阶段。性能优化

getDefaultProps()方法被调用一次并缓存起来——在多个类实例之间共享。在组件的任何实例被建立以前,咱们(的代码逻辑)不能依赖这里的this.props。这个方法返回一个对象而且属性若是没有经过父组件传入的话相应的属性会挂载到this.props对象上。app

getInitialState()方法也只会被调用一次,(调用时机)恰好是mounting阶段开始以前。返回值将会被当成this.state的初始值,且必须是一个对象。函数

如今咱们来证实上面的猜测,实现一个显示的值能够被增长和减小的组件,基本上就是一个拥有“+”和“-”按钮的计数器。

var Counter = React.createClass({
    getDefaultProps: function() {
        console.log('getDefaultProps');
        return {
            title: 'Basic counter!!!'
        }
    },

    getInitialState: function() {
        console.log('getInitialState');
        return {
            count: 0
        }
    },

    render: function() {
        console.log('render');
        return (
            <div>
                <h1>{this.props.title}</h1>
                <div>{this.state.count}</div>
                <input type='button' value='+' onClick={this.handleIncrement} />
                <input type='button' value='-' onClick={this.handleDecrement} />
            </div>
        );
    },

    handleIncrement: function() {
        var newCount = this.state.count + 1;
        this.setState({count: newCount});
    },

    handleDecrement: function() {
        var newCount = this.state.count - 1;
        this.setState({count: newCount});
    },

    propTypes: {
        title: React.PropTypes.string
    }
});

ReactDOM.render(
    React.createElement(Counter),
    document.getElementById('app-container')
);

咱们经过getDefaultProps()方法配置一个“title”属性,若是没有传入则提供一个默认值。而后经过getInitialState()为组件设置一个初始state值“{count: 0}”。若是运行这段代码你将会看到控制台输出以下结果:

如今咱们想要让Counter组件能够设置this.state.count初始值和增长/减小的步长值,但依然提供一个默认值:

var Component = React.createClass({
    getDefaultProps: function() {
        console.log('getDefaultProps');
        return {
            title: "Basic counter!!!",
            step: 1
        }
    },

    getInitialState: function() {
        console.log('getInitialState');
        return {
            count: (this.props.initialCount || 0)
        };
    },

    render: function() {
        console.log('render');
        var step = this.props.step;

        return (
            <div>
                <h1>{this.props.title}</h1>
                <div>{this.state.count}</div>
                <input type='button' value='+' onClick={this.updateCounter.bind(this, step)} />
                <input type='button' value='-' onClick={this.updateCounter.bind(this, -step)} />
            </div>
        );
    },

    updateCounter: function(value) {
        var newCount = this.state.count + value;
        this.setState({count: newCount});
    },

    propTypes: {
        title: React.PropTypes.string,
        initialCount: React.PropTypes.number,
        step: React.PropTypes.number
    }
});

ReactDOM.render(
    React.createElement(Component, {initialCount: 5, step: 2}),
    document.getElementById('app-container')
);

这里经过Function.prototype.bind使用偏函数应用(Partial Application)来达到复用代码的目的。

如今咱们拥有了一个可定制化的组件。

增加(Mounting)阶段

Mounting阶段发生在组件即将被插入到DOM以前。这个阶段有两个方法能够用:componentWillMount()componentDidMount()

componentWillMount()方法是这个阶段最早调用的,它只在恰好初始渲染(initial rendering)发生以前被调用一次,也就是React在DOM插入组件以前。须要注意的是在此处调用this.setState()方法将不会触发重复渲染(re-render)。若是添加下面的代码到计数器组件咱们将会看到此方法在getInitialState()以后且render()以前被调用。

getInitialState: function() {...},
componentWillMount: function() {
    console.log('componentWillMount');
},

componentDidMount()是这个阶段第二个被调用的方法,恰好发生在React插入组件到DOM以后,且也只被调用一次。如今能够更新DOM元素了,这意味着这个方法是初始化其余须要访问DOM或操做数据的第三方库的最佳时机。

假设咱们想要经过API拉取数据来初始化组件。咱们应该直接在计数器组件的componentDidMount()方法拉取数据,可是这让组件看起来有太多逻辑了,更可取的方案是使用容器组件来作:

var Container = React.createClass({
    getInitialState: function() {
        return {
            data: null,
            fetching: false,
            error: null
        };
    },

    render: function() {
        if (this.props.fetching) {
            return <div>Loading...</div>;
        }

        if (this.props.error) {
            return (
                <div className='error'>
                    {this.state.error.message}
                </div>
            );
        }

        return <Counter {...data} />
    },

    componentDidMount: function() {
        this.setState({fetching: true});

        Axios.get(this.props.url).then(function(res) {
            this.setState({data: res.data, fetching: false});
        }).catch(function(res) {
            this.setState({error: res.data, fetching: false});
        });
    }
});

Axios是一个基于priomise的跨浏览器和Node.js的HTTP客户端。

 更新阶段

当组件的属性或者状态更新时也须要一些方法来供咱们执行代码,这些方法也是组件更新阶段的一部分且按照如下的顺序被调用:

一、当从父组件接收到新的属性时:

props updated

二、当经过this.setState()改变状态时:

state updated

此阶段React组件已经被插入DOM了,所以这些方法将不会在首次render时被调用。

最早被调用的方法是componentWillReceiveProps(),当组件接收到新属性时被调用。咱们能够利用此方法为React组件提供一个在render以前修改state的机会。在此方法内调用this.setState()将不会致使重复render,而后能够经过this.props访问旧的属性。例如计数器组件,若是咱们想要在任什么时候候父组件传入“initialCount”时更新状态,能够这样作:

...
componentWillReceiveProps: function(newProps) {
    this.setState({count: newProps.initialCount});
},
...

shouldComponentUpdate()方法容许咱们自行决定下一个state更新时是否触发重复render。此方法返回一个布尔值,且默认是true。可是咱们也能够返回false,这样下面的(生命周期)方法将不会被调用:

  • componentWillUpdate()

  • render()

  • componentDidUpdate()

当有性能瓶颈时也可使用shouldComponentUpdate()方法(来优化)。尤为是数百个组件一块儿时从新render的代价将会十分昂贵。为了证实这个猜测咱们来看一个例子:

var TextComponent = React.createClass({
    shouldComponentUpdate: function(nextProps, nextState) {
        if (this.props.text === nextProps.text) return false;
        return true;
    },

    render: function() {
        return <textarea value={this.props.text} />;
    }
});

此例中不管什么时候父组件传入一个“text”属性到TextComponent而且text属性等于当前的“text”属性时,组件将会不会重复render。

当接收到新的属性或者state时在render以前会马上调用componentWillUpdate()方法。能够利用此时机来为更新作一些准备工做,虽然这个阶段不能调用this.setState()方法:

...
componentWillUpdate: function(nextProps, nextState) {
    console.log('componentWillUpdate', nextProps, nextState);
},
...

componentDidUpdate()方法在React更新DOM以后马上被调用。能够在此方法里操做被更新过的DOM或者执行一些后置动做(action)。此方法有两个参数:

  1. prevProps:旧的属性

  2. prevState:旧的state

这个方法的一个常见使用场景是当咱们使用须要操做更新后的DOM才能工做的第三方库——如jQuery插件的时候。在componentDidMount()方法内初始化第三方库,可是在属性或state更新触发DOM更新以后也须要同步更新第三方库来保持接口一致,这些必须在componentDidUpdate()方法内来完成。为了验证这一点,让咱们看看如何开发一个Select2库包裹(wrapper)React组件:

var Select2 = React.createClass({
    componentDidMount: function() {
        $(this._ref).select2({data: this.props.items});
    },

    render: function() {
        return (
            <select
                ref={
                    function(input) {
                        this._ref = input;
                    }.bind(this)
                }>
            </select>
        );
    },

    componentDidUpdate: function() {
        $(this._ref).select2('destroy');
        $(this._ref).select2({data: this.props.items});
    }
});

卸载阶段(unmounting)

此阶段React只提供了一个方法:

  • componentWillUnmount()

它将在组件从DOM卸载以前被调用。能够在内部执行任何可能须要的清理工做,如无效的计数器或者清理一些在componentDidMount()/componentDidUpdate()内建立的DOM。好比在Select2组件里边咱们能够这样子:

...
componetWillUnmount: function(){
   $(this._ref).select2('destroy');
},
...

概述

React为咱们提供了一种在建立组件时申明一些将会在组件生命周期的特定时机被自动调用的方法的可能。如今咱们很清晰的理解了每个组件生命周期方法所扮演的角色以及他们被调用的顺序。这使咱们有机会在组件建立和销毁时执行一些操做。也容许咱们在当属性和状态变化时作出相应的反应从而更容易的整合第三方库和追踪性能问题。

但愿您以为此文对您有用,若是是这样,请推荐之!!!

相关文章
相关标签/搜索