1. 程式人生 > >react 生命週期 —— 從原始碼理解

react 生命週期 —— 從原始碼理解

react生命週期理解

渲染的過程

react 生命週期在不同狀態下的執行順序

  • 當首次裝載元件時,按順序執行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount;

  • 當解除安裝元件時,執行 componentWillUnmount;

  • 當重新裝載元件時,此時按順序執行 getInitialState、componentWillMount、render 和 componentDidMount,但並不執行 getDefaultProps;

  • 當再次渲染元件時,元件接受到更新狀態,此時按順序執行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。

疑問

  • 為何 React 會按上述順序執行生命週期?

  • 為何 React 多次 render 時,會執行生命週期的不同階段?

  • 為何 getDefaultProps 只執行了1次?

詳解

自定義元件的生命週期主要通過三種狀態進行管理:MOUNTING,RECIVE_PROPS,UNMOUNTING;他們負責通知元件當前所處的狀態,應該執行生命週期中的哪個步驟,是否可以更新 state 。
三個狀態對應三種方法:

  • MOUNTING -> mountComponent
  • RECIVE_PROPS -> updateComponent
  • UNMOUNTING -> unmountComponent

每個方法都提供了兩種處理方法,will 在進入狀態之前呼叫,did 在進入狀態之後呼叫

createClass 建立自定義元件

createClass 建立自定義元件的入口方法,負責管理生命週期中的 getDefaultProps。getDefaultProps() 只執行一次,這樣所有例項初始化的 props 將會共享。

通過 createClass 建立自定義元件,利用原型繼承 ReactCompositeComponentBase 父類,按順序合併 mixins,設定初始化 defaultProps,建立元素ReactElement.

當使用 ES6 classes 編寫 React 元件時,其實就是呼叫內部方法 createClass 建立元件, 該方法返回一個Constructor(props, context, updater) 用來生成元件例項,我們發現在呼叫React.createClass,已經執行了getDefaultProps() 和 getInitialState() ,並將其賦值於Constructor的原型中

// ReactCompositeComponent 的基類
var ReactCompositeComponentBase = function() {};

// 將 Mixin 合併到 ReactCompositeComponentBase 的原型上
assign(
  ReactCompositeComponentBase.prototype,
  ReactComponent.Mixin,
  ReactOwner.Mixin,
  ReactPropTransferer.Mixin,
  ReactCompositeComponentMixin
);

var ReactCompositeComponent = {
  LifeCycle: CompositeLifeCycle,
  Base: ReactCompositeComponentBase,

  // 建立元件
  createClass: function(spec) {
    // 建構函式
    var Constructor = function(props, context) {
      this.props = props;
      this.context = context;
      this.state = null;
      var initialState = this.getInitialState ? this.getInitialState() : null;
      this.state = initialState;
    };

    // 原型繼承父類
    Constructor.prototype = new ReactCompositeComponentBase();
    Constructor.prototype.constructor = Constructor;

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

    // mixins 合併後裝載 defaultProps (React整個生命週期中 getDefaultProps 只執行一次)
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

    for (var methodName in ReactCompositeComponentInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return ReactElement.createFactory(Constructor);
  }
}

狀態一 MOUNTING

mountComponent 負責管理生命週期中的 getInitialState、componentWillMount、render 和 componentDidMount。

由於 getDefaultProps 是通過 Constrouctor 進行管理,因此也是整個生命週期中最先開始執行的。mountComponent 無法呼叫到 getDefaultProps ,所以getDefaultProps 只執行一次。

由於通過 ReactCompositeComponentBase 返回的是一個虛擬節點,因此需要利用 instantiateReactComponent 去得到例項,再使用 mountComponent 拿到結果作為當前自定義元素的結果。

首先通過 mountComponent 裝載元件,此時,將狀態設定為 MOUNTING ,利用 getInitialState 獲取初始 state, 初始化更新佇列。

若存在 componentWillMount ,則執行。如果此時在 componentWillMount 中呼叫 setState 是不會觸發 reRender, 而是進行 state 合併。

到此時,已經完成 MOUNTING 的工作,更新狀態為 NULL,同時 state 也將執行更新操作,此刻在 render 中可以獲取更新後的 this.state 資料。

其實,mountComponent 本質上是通過 遞迴渲染 內容的,由於遞迴的特性,父元件的 componentWillMount 一定在其子元件的 componentWillMount 之前呼叫,而父元件的 componentDidMount 肯定在其子元件的 componentDidMount 之後呼叫。

當渲染完成之後,若存在 componentDidMount 則觸發。這就解釋了 componentWillMount - render - componentDidMount 三者之間的執行順序。

// 裝載元件
mountComponent: function(rootID, transaction, mountDepth) {
  // 當前狀態為 MOUNTING
  this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;

  // 當前元素對應的上下文
  this.context = this._processContext(this._currentElement._context);

  // 當前元素對應的 props
  this.props = this._processProps(this.props);

  // 獲取初始化 state
  this.state = this.getInitialState();

  // 初始化更新佇列
  this._pendingState = null;
  this._pendingForceUpdate = false;

  // componentWillMount 呼叫setstate,不會觸發rerender而是自動提前合併
  if (this.componentWillMount) {
    this.componentWillMount();
    if (this._pendingState) {
      this.state = this._pendingState;
      this._pendingState = null;
    }
  }

  // 得到 _currentElement 對應的 component 類例項
  this._renderedComponent = instantiateReactComponent(
    this._renderValidatedComponent(),
    this._currentElement.type
  );

  // 完成 MOUNTING,更新 state
  this._compositeLifeCycleState = null;

  // render 遞迴渲染
  var markup = this._renderedComponent.mountComponent(
    rootID,
    transaction,
    mountDepth + 1
  );

  // 如果存在 this.componentDidMount,則渲染完成後觸發
  if (this.componentDidMount) {
    transaction.getReactMountReady().enqueue(this.componentDidMount, this);
  }

  return markup;
}

狀態二:RECEIVE_PROPS

updateComponent 負責管理生命週期中的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。

首先通過 updateComponent 更新元件,如果前後元素不一致說明元件需要更新,此時將狀態更新為 RECEIVE_PROPS。

如果存在 componentWillReceiveProps,則執行。如果此時在 componentWillReceiveProps 中呼叫 setState ,不會觸發 reRender ,只會合併 state。

到此時,已經完成 RECEIVE_PROPS ,更新狀態為 null ,同時 state 也將執行更新操作,此時呼叫 this.state 會獲取到更新後的 資料。

注意:此時 this.state 雖然獲取到更新資料,但只能在內部原始碼中使用,我們在開發時,若在 componentWillReceiveProps 中呼叫 setState,那麼在
componentWillReceiveProps、shouldComponentUpdate 和 componentWillUpdate 中還是無法獲取到更新後的 this.state,即此時訪問的 this.state
仍然是未更新的資料,只有在 render 和 componentDidUpdate 中才能獲取到更新後的 this.state。

呼叫 shouldComponentUpdate 判斷是否需要進行元件更新,如果存在 componentWillUpdate,則執行。

updateComponent 本質上也是通過 遞迴渲染 內容的,由於遞迴的特性,父元件的 componentWillUpdate 一定在其子元件的 componentWillUpdate 之前呼叫,而父元件的 componentDidUpdate 肯定在其子元件 componentDidUpdate 之後呼叫。

當渲染完成之後,若存在 componentDidUpdate ,則執行。這就解釋了 componentWillReceiveProps - componentWillUpdate - render - componentDidUpdate 它們之間的執行順序。

注意:禁止在 shouldComponentUpdate 和 componentWillUpdate 中呼叫setState,會造成迴圈呼叫,直至耗光瀏覽器記憶體後崩潰。

// 更新元件
updateComponent: function(transaction, prevParentElement, nextParentElement) {
  var prevContext = this.context;
  var prevProps = this.props;
  var nextContext = prevContext;
  var nextProps = prevProps;

  if (prevParentElement !== nextParentElement) {
    nextContext = this._processContext(nextParentElement._context);
    nextProps = this._processProps(nextParentElement.props);
    // 當前狀態為 RECEIVING_PROPS
    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;

    // 如果存在 componentWillReceiveProps,則執行
    if (this.componentWillReceiveProps) {
      this.componentWillReceiveProps(nextProps, nextContext);
    }
  }

  // 設定狀態為 null,更新 state
  this._compositeLifeCycleState = null;
  var nextState = this._pendingState || this.state;
  this._pendingState = null;
  var shouldUpdate =
    this._pendingForceUpdate ||
    !this.shouldComponentUpdate ||
    this.shouldComponentUpdate(nextProps, nextState, nextContext);
  if (!shouldUpdate) {
    // 如果確定元件不更新,仍然要設定 props 和 state
    this._currentElement = nextParentElement;
    this.props = nextProps;
    this.state = nextState;
    this.context = nextContext;
    this._owner = nextParentElement._owner;
    return;
  }
  this._pendingForceUpdate = false;

  ......

  // 如果存在 componentWillUpdate,則觸發
  if (this.componentWillUpdate) {
    this.componentWillUpdate(nextProps, nextState, nextContext);
  }

  // render 遞迴渲染
  var nextMarkup = this._renderedComponent.mountComponent(
    thisID,
    transaction,
    this._mountDepth + 1
  );

  // 如果存在 componentDidUpdate,則觸發
  if (this.componentDidUpdate) {
    transaction.getReactMountReady().enqueue(
      this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
      this
    );
  }
}

狀態三:UNMOUNTING

unmountComponent 負責管理生命週期中的 componentWillUnmount。

首先將狀態設定為 UNMOUNTING,若存在 componentWillUnmount,則執行;如果此時在 componentWillUnmount 中呼叫 setState,是不會觸發 reRender,因為所有更新佇列和更新狀態都被重置為 NULL,完成了元件解除安裝操作。

// 解除安裝元件
unmountComponent: function() {
  // 設定狀態為 UNMOUNTING
  this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;

  // 如果存在 componentWillUnmount,則觸發
  if (this.componentWillUnmount) {
    this.componentWillUnmount();
  }

  // 更新狀態為 null
  this._compositeLifeCycleState = null;
  this._renderedComponent.unmountComponent();
  this._renderedComponent = null;

  ReactComponent.Mixin.unmountComponent.call(this);
}

setState 更新機制

當呼叫 setState 時,會對 state 以及 _pendingState 更新佇列進行合併操作,但其實真正更新 state 的幕後黑手是 replaceState。

replaceState 會先判斷當前狀態是否為 MOUNTING,如果不是即會呼叫ReactUpdates.enqueueUpdate 執行更新。

當狀態不為 MOUNTING 或 RECEIVING_PROPS 時,performUpdateIfNecessary 會獲取 _pendingElement、_pendingState、_pendingForceUpdate,並呼叫 updateComponent 進行元件更新。

如果在 shouldComponentUpdate 或 componentWillUpdate 中呼叫 setState,此時的狀態已經從 RECEIVING_PROPS -> NULL,則 performUpdateIfNecessary 就會呼叫 updateComponent 進行元件更新,但 updateComponent 又會呼叫 shouldComponentUpdate 和 componentWillUpdate,因此造成迴圈呼叫,使得瀏覽器記憶體佔滿後崩潰。

// 更新 state
replaceState: function(completeState, callback) {
  validateLifeCycleOnReplaceState(this);

  // 更新佇列
  this._pendingState = completeState;

  // 判斷狀態是否為 MOUNTING,如果不是,即可執行更新
  if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
    ReactUpdates.enqueueUpdate(this, callback);
  }
},

// 如果存在 _pendingElement、_pendingState、_pendingForceUpdate,則更新元件
performUpdateIfNecessary: function(transaction) {
  var compositeLifeCycleState = this._compositeLifeCycleState;

  // 當狀態為 MOUNTING 或 RECEIVING_PROPS時,則不更新
  if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
      compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
    return;
  }

  var prevElement = this._currentElement;
  var nextElement = prevElement;
  if (this._pendingElement != null) {
    nextElement = this._pendingElement;
    this._pendingElement = null;
  }

  // 呼叫 updateComponent
  this.updateComponent(
    transaction,
    prevElement,
    nextElement
  );
}

總結

  • React 通過三種狀態:MOUNTING、RECEIVE_PROPS、UNMOUNTING,管理整個生命週期的執行順序;

  • setState 會先進行 _pendingState 更新佇列的合併操作,不會立刻 reRender,因此是非同步操作,且通過判斷狀態(MOUNTING、RECEIVE_PROPS)來控制 reRender 的時機;

  • 不建議在 getDefaultProps、getInitialState、shouldComponentUpdate、componentWillUpdate、render 和 componentWillUnmount 中呼叫 setState,特別注意:不能在 shouldComponentUpdate 和 componentWillUpdate 中呼叫 setState,會導致迴圈呼叫。