React生命週期淺談

  React學習過程當中,對於組件最重要的(也多是之一)的莫過因而組件的生命週期了,React至關於使用狀態來映射到界面輸出,經過狀態的改變從而改變界面效果。在狀態的改變過程當中,必需要經歷組件的生命週期。javascript

  React會經歷三個階段:mount、update、unmount,每一個階段對應兩個生命週期(ummount除外):Will(對應進入)與Did(對應結束),於是存在五個對應的方法,而且在update階段存在兩種特殊的方法:shouldComponentUpdatecomponentWillReceiveProps,這
些函數基本構成了React的生命週期。以下圖所示:java

life-cycle

  上圖中的getDefaultPropsgetInitialState分別對應ES6中的static defaultProps = {}與構造函數construct中的this.state ={}賦值。下面咱們按照上圖的過程依次介紹:(介紹主要以React.createClass爲例,基本等同於extends React.Component)react

React生命週期

初次渲染

//本文代碼基於15.0,只刪選其中有用的部分,註釋來源於《深刻React技術棧》
var React = {
  //...
  createClass: ReactClass.createClass
  //...
};

var ReactClass = {
  // 建立自定義組件
  createClass: function(spec) {
    var Constructor = function(props, context, updater) {
      // 自動綁定
      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
      this.state = null;

      // ReactClass 沒有構造函數,經過 getInitialState 和 componentWillMount 來代替
      var initialState = this.getInitialState ? this.getInitialState() : null;
      this.state = initialState;
    };

    // 原型繼承父類
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];

    // 合併 mixin
    injectedMixins.forEach(
      mixSpecIntoComponent.bind(null, Constructor)
    );

    mixSpecIntoComponent(Constructor, spec);

    // 全部 mixin 合併後初始化 defaultProps(在整個生命週期中,getDefaultProps 只執行一次)
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }
    // 減小查找並設置原型的時間
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }
    return Constructor;
  },
};

  總結一下上面的代碼React.createClass返回函數Constructor(props, context, updater)用來生成組件的實例,而React.createClass執行的時候會執行包括:合併mixin,獲取默認屬性defaultProps將其賦值到Constructor的原型中,而且也將傳入React.createClass中的方法賦值到Constructor的原型中,以縮短再次查找方法的時間。
  在這個函數中,咱們關心的部分其實主要集中在:瀏覽器

if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
}

  咱們發如今調用React.createClass,已經執行了getDefaultProps(),並將其賦值於Constructor的原型中,因此咱們對照聲明週期圖能夠獲得:app

React中的getDefaultProps()僅會在整個生命週期中只執行一次,而且初始化的實例都會共享該defaultPropsless

  ReactCompositeComponent中的mountComponentupdateComponentunmountComponent分別對應於React中mount、update、unmount階段的處理,首先大體看一下mount階段的簡要代碼:函數

// 當組件掛載時,會分配一個遞增編號,表示執行 ReactUpdates 時更新組件的順序
var nextMountID = 1;

var ReactCompositeComponent = {
    /**
     * 組件初始化,渲染、註冊事件
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @param {?object} hostParent
     * @param {?object} hostContainerInfo
     * @param {?object} context
     * @return {?string} 返回的markup會被插入DOM中.
     * @final
     * @internal
     */
    mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) {
        // 當前元素對應的上下文
        this._context = context;
        this._mountOrder = nextMountID++;
        this._nativeParent = nativeParent;
        this._nativeContainerInfo = nativeContainerInfo;

        var publicProps = this._processProps(this._currentElement.props);
        var publicContext = this._processContext(context);

        var Component = this._currentElement.type;

        // 初始化公共類
        var inst = this._constructComponent(publicProps, publicContext);
        var renderedElement;

        // 用於判斷組件是否爲 stateless,無狀態組件沒有狀態更新隊列,它只專一於渲染
        if (!shouldConstruct(Component) && (inst == null || inst.render == null)) {
            renderedElement = inst;
            warnIfInvalidElement(Component, renderedElement);
            inst = new StatelessComponent(Component);
        }

        // 這些初始化參數本應該在構造函數中設置,在此設置是爲了便於進行簡單的類抽象
        inst.props = publicProps;
        inst.context = publicContext;
        inst.refs = emptyObject;
        inst.updater = ReactUpdateQueue;

        this._instance = inst;

        // 將實例存儲爲一個引用
        ReactInstanceMap.set(inst, this);

        // 初始化 state
        var initialState = inst.state;
        if (initialState === undefined) {
            inst.state = initialState = null;
        }

        // 初始化更新隊列
        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;

        var markup;
        // 若是掛載時出現錯誤
        if (inst.unstable_handleError) {
            markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent,
                nativeContainerInfo, transaction, context);
        } else {
            // 執行初始化掛載
            markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
                context);
        }

        // 若是存在 componentDidMount,則調用
        if (inst.componentDidMount) {
            transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
        }

        return markup;
    },

    performInitialMountWithErrorHandling: function (renderedElement, nativeParent, nativeContainerInfo,
                                                    transaction, context) {
        var markup;
        var checkpoint = transaction.checkpoint();

        try {
            // 捕捉錯誤,若是沒有錯誤,則初始化掛載
            markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
                context);
        } catch (e) {
            transaction.rollback(checkpoint);
            this._instance.unstable_handleError(e);
            if (this._pendingStateQueue) {
                this._instance.state = this._processPendingState(this._instance.props, this._instance.context);
            }
            checkpoint = transaction.checkpoint();

            // 若是捕捉到錯誤,則執行 unmountComponent 後,再初始化掛載
            this._renderedComponent.unmountComponent(true);
            transaction.rollback(checkpoint);

            markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
                context);
        }
        return markup;
    },

    performInitialMount: function (renderedElement, nativeParent, nativeContainerInfo, transaction,
                                   context) {
        var inst = this._instance;
        // 若是存在 componentWillMount,則調用
        if (inst.componentWillMount) {
            inst.componentWillMount();
            // componentWillMount 調用 setState 時,不會觸發 re-render 而是自動提早合併
            if (this._pendingStateQueue) {
                inst.state = this._processPendingState(inst.props, inst.context);
            }
        }

        // 若是不是無狀態組件,便可開始渲染
        if (renderedElement === undefined) {
            renderedElement = this._renderValidatedComponent();
        }

        this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
        // 獲得 _currentElement 對應的 component 類實例
        this._renderedComponent = this._instantiateReactComponent(
            renderedElement
        );
        // render 遞歸渲染
        var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent,
            nativeContainerInfo, this._processChildContext(context));

        return markup;
    }
}

  咱們如今只關心生命週期相關的代碼,初始化及其餘的代碼暫時不考慮,咱們發現初始化state以後會進入渲染的步驟,根據是否存在錯誤,選擇性執行performInitialMountWithErrorHandlingperformInitialMount,咱們僅考慮正常狀況下的performInitialMountoop

// 若是存在 componentWillMount,則調用
 if (inst.componentWillMount) {
     inst.componentWillMount();
     // componentWillMount 調用 setState 時,不會觸發 re-render 而是自動提早合併
     if (this._pendingStateQueue) {
         inst.state = this._processPendingState(inst.props, inst.context);
     }
 }

  若是存在componentWillMount則執行,若是在componentWillMount執行了setState方法,在componentWillMount並不會獲得已經更新的state,由於咱們發現的state的合併過程是在componentWillMount結束後才執行的。而後在performInitialMount(爲例)會進行遞歸渲染,
而後在遞歸執行結束後,返回markup(返回的markup會被插入DOM中)。而後,若是存在 componentDidMount。而且因爲渲染的過程都是遞歸的,咱們能夠綜合獲得渲染階段的生命週期(包括子節點)以下:學習

更新階段

  首先仍是看一下簡要的更新階段的代碼:this

var ReactCompositeComponent = {
    /**
     * 更新已經渲染的組件。componentWillReceiveProps和shouldComponentUpdate方法將會被調用
     * 而後,(更新的過程沒有被省略),其他的更新階段的生命週期都會被調用,對應的DOM會被更新。
     * @param {ReactReconcileTransaction} transaction
     * @param {ReactElement} prevParentElement
     * @param {ReactElement} nextParentElement
     * @internal
     * @overridable
     */

    updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
        var inst = this._instance;
        var willReceive = false;
        var nextContext;
        var nextProps;

        // Determine if the context has changed or not
        if (this._context === nextUnmaskedContext) {
            nextContext = inst.context;
        } else {
            nextContext = this._processContext(nextUnmaskedContext);
            willReceive = true;
        }

        var prevProps = prevParentElement.props;
        var nextProps = nextParentElement.props;

        // Not a simple state update but a props update
        if (prevParentElement !== nextParentElement) {
            willReceive = true;
        }

        // 若是存在 componentWillReceiveProps,則調用
        if (willReceive && inst.componentWillReceiveProps) {
            inst.componentWillReceiveProps(nextProps, nextContext);
        }

        // 將新的 state 合併到更新隊列中,此時 nextState 爲最新的 state
        var nextState = this._processPendingState(nextProps, nextContext);

        // 根據更新隊列和 shouldComponentUpdate 的狀態來判斷是否須要更新組件
        var shouldUpdate =
            this._pendingForceUpdate || !inst.shouldComponentUpdate ||
            inst.shouldComponentUpdate(nextProps, nextState, nextContext);

        if (shouldUpdate) {
            // 重置更新隊列
            this._pendingForceUpdate = false;
            // 即將更新 this.props、this.state 和 this.context
            this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction,
                nextUnmaskedContext);
        } else {
            // 若是肯定組件不更新,仍然要設置 props 和 state
            this._currentElement = nextParentElement;
            this._context = nextUnmaskedContext;
            inst.props = nextProps;
            inst.state = nextState;
            inst.context = nextContext;
        }
    },

    //當肯定組件須要更新時,則調用
    _performComponentUpdate: function (nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) {
        var inst = this._instance;
        var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
        var prevProps;
        var prevState;
        var prevContext;

        // 若是存在 componentDidUpdate,則將當前的 props、state 和 context 保存一份
        if (hasComponentDidUpdate) {
            prevProps = inst.props;
            prevState = inst.state;
            prevContext = inst.context;
        }

        // 若是存在 componentWillUpdate,則調用
        if (inst.componentWillUpdate) {
            inst.componentWillUpdate(nextProps, nextState, nextContext);
        }

        this._currentElement = nextElement;
        this._context = unmaskedContext;

        // 更新 this.props、this.state 和 this.context
        inst.props = nextProps;
        inst.state = nextState;
        inst.context = nextContext;

        // 實現代碼省略,遞歸調用 render 渲染組件
        this._updateRenderedComponent(transaction, unmaskedContext);

        // 當組件完成更新後,若是存在 componentDidUpdate,則調用
        if (hasComponentDidUpdate) {
            transaction.getReactMountReady().enqueue(
                inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
                inst
            );
        }
    }
}

  判斷更新階段是否須要調用componentWillReceiveProps主要經過以下,一樣咱們只關心生命週期相關的代碼,其餘的代碼暫時不考慮:

if (this._context === nextUnmaskedContext) {
    nextContext = inst.context;
} else {
    nextContext = this._processContext(nextUnmaskedContext);
    willReceive = true;
}

var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;

// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
    willReceive = true;
}

// 若是存在 componentWillReceiveProps,則調用
if (willReceive && inst.componentWillReceiveProps) {
    inst.componentWillReceiveProps(nextProps, nextContext);
}

  因此咱們能夠分析得出只有在context發生變化或者parentElement先後不一致(prevParentElement !== nextParentElement)時,willReceive才爲true,這時,若是存在componentWillReceiveProps,就會被調用。那麼咱們須要瞭解的是parentElement存儲的是什麼信息,parentElement存儲的信息以下:

{
    $$typeof:Symbol(react.element),
    key:null,
    props:Object,
    ref:null,
    type: function Example(props),
    _owner:ReactCompositeComponentWrapper,
    _store:Object,
    _self:App,
    _source:Object,
    __proto__:Object
}

  咱們發現,parentElement是不含父組件的state信息的。所以咱們還能夠獲得下面的結論: 若是父組件的props等信息發生改變時,即便這個改變的屬性沒有傳入到子組件,但也會引發子組件的componentWillReceiveProps的執行
而且咱們能夠發現,若是在componentWillReceiveProps中調用setState,state是不會當即獲得更新。state會在componentWillReceiveProps後合併,因此componentWillReceiveProps中是不能拿到新的state

須要注意的是

不能在 shouldComponentUpdatecomponentWillUpdate 中調用 setState,緣由是shouldComponentUpdatecomponentWillUpdate調用setState會致使再次調用updateComponent,這會形成循環調用,直至耗光瀏覽器內存後崩潰。

var shouldUpdate =
    this._pendingForceUpdate || !inst.shouldComponentUpdate ||
    inst.shouldComponentUpdate(nextProps, nextState, nextContext);

if (shouldUpdate) {
    // 重置更新隊列
    this._pendingForceUpdate = false;
    // 即將更新 this.props、this.state 和 this.context
    this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction,
        nextUnmaskedContext);
} else {
    // 若是肯定組件不更新,仍然要設置 props 和 state
    this._currentElement = nextParentElement;
    this._context = nextUnmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;
}

  而後咱們會根據shouldComponentUpdate返回的內容,決定是否執行所有的聲明週期更新操做。若是返回false,就不會執行接下來的更新操做。可是,從上面看得出,即便shouldComponentUpdate返回了false,組件中的propsstate以及state的都會被更新(固然,調用了forceUpdate函數的話,會跳過shouldComponentUpdate的判斷過程。)
若是shouldComponentUpdate返回true或者沒有定義shouldComponentUpdate函數,就會進行進行組件更新。若是存在componentDidUpdate,會將更新前的statepropscontext保留一份備份。若是存在componentWillUpdate,則調用。接着遞歸調用render進行渲染更新。當組件完成更新後,若是存在componentDidUpdate函數就會被調用,
並將更新前的狀態備份和當前的狀態做爲參數傳遞。

卸載階段

var ReactCompositeComponent = {
    /**
     * 釋放由`mountComponent`分配的資源.
     *
     * @final
     * @internal
     */
    unmountComponent: function(safely) {
        if (!this._renderedComponent) {
            return;
        }
        var inst = this._instance;
        // 若是存在 componentWillUnmount,則調用
        if (inst.componentWillUnmount) {
            if (safely) {
                var name = this.getName() + '.componentWillUnmount()';
                ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
            } else {
                inst.componentWillUnmount();
            }
        }
        // 若是組件已經渲染,則對組件進行 unmountComponent 操做
        if (this._renderedComponent) {
            ReactReconciler.unmountComponent(this._renderedComponent, safely);
            this._renderedNodeType = null;
            this._renderedComponent = null;
            this._instance = null;
        }
        // 重置相關參數、更新隊列以及更新狀態
        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;
        this._pendingCallbacks = null;
        this._pendingElement = null;
        this._context = null;
        this._rootNodeID = null;
        this._topLevelWrapper = null;
        // 清除公共類
        ReactInstanceMap.remove(inst);
    }
}

  卸載階段很是簡單,若是存在componentWillUnmount函數,則會在更新前調用。而後遞歸調用清理渲染。最後將相關參數、更新隊列以及更新狀態進行重置爲空。
  原本想接着寫一下setState和React Transaction,發現太弱雞了,並無看懂,如今正在學習研究中,你們之後能夠關注一下~

相關文章
相關標籤/搜索