【譯】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爲咱們提供了一種在建立組件時申明一些將會在組件生命週期的特定時機被自動調用的方法的可能。如今咱們很清晰的理解了每個組件生命週期方法所扮演的角色以及他們被調用的順序。這使咱們有機會在組件建立和銷燬時執行一些操做。也容許咱們在當屬性和狀態變化時作出相應的反應從而更容易的整合第三方庫和追蹤性能問題。

但願您以爲此文對您有用,若是是這樣,請推薦之!!!

相關文章
相關標籤/搜索