React-原始碼解析-生命週期(自定義元件)

mazy發表於2018-06-22

image

自定義元件(生命週期)


createClass 是建立自定義元件的入口方法,負責管理生命週期中的 getDefaultProps。該方 法在整個生命週期中只執行一次,這樣所有例項初始化的 props 將會被共享。 通過 createClass 建立自定義元件,利用原型繼承 ReactClassComponent 父類,按順序合併 mixin,設定初始化 defaultProps,返回建構函式。 當使用 ES6 classes 編寫 React 元件時,class MyComponent extends React.Component 其實就 是呼叫內部方法 createClass 建立元件


  /**
   * 建立自定義元件
   */
  function createClass(spec) {
    //建立自定義元件
    var Constructor = identity(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, IsMountedMixin);
    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;
  }
複製程式碼

生命週期-掛載階段(MOUNTING)


  1. mountComponent 負責管理生命週期中的 getInitialState、componentWillMount、render 和 componentDidMount。
  2. 由於 getDefaultProps 是通過建構函式進行管理的,所以也是整個生命週期中最先開始執行的。而 mountComponent 只能望洋興嘆,無法呼叫到 getDefaultProps。這就解釋了為何 getDefaultProps只執行一次。
  3. 由於通過 ReactCompositeComponentBase 返回的是一個虛擬節點,所以需要利用 instantiateReactComponent去得到例項,再使用 mountComponent 拿到結果作為當前自定義元素的結果。
  4. 通過 mountComponent 掛載元件,初始化序號、標記等引數,判斷是否為無狀態元件,並進行對應的元件初始化工作,比如初始化 props、context 等引數。利用 getInitialState 獲取初始化state、初始化更新佇列和更新狀態。
  5. 若存在 componentWillMount,則執行。如果此時在 componentWillMount 中呼叫 setState 方法, 是不會觸發 re-render的,而是會進行 state 合併,且 inst.state = this._processPendingState(inst.props, inst.context) 是在 componentWillMount 之後執行的, 因此 componentWillMount 中的 this.state 並不是最新的,在 render 中才可以獲取更新後的 this.state。因此,React 是利用更新佇列 this._pendingStateQueue 以及更新狀態 this._pendingReplaceState 和 this._pendingForceUpdate 來實現 setState 的非同步更新機制。
  6. 當渲染完成後,若存在 componentDidMount,則呼叫。這就解釋了 componentWillMount 、render 、componentDidMount 這三者之間的執行順序。
  7. mountComponent 本質上是通過遞迴渲染內容的,由於遞迴的特性,父元件的componentWillMount 在其子元件的 componentWillMount 之前呼叫,而父元件的 componentDidMount在其子元件的 componentDidMount 之後呼叫。

  /**
   * 初始化元件,渲染標記,註冊事件監聽器
   */
  mountComponent: function(transaction,hostParent, hostContainerInfo, context, ) {
    //當前元素對應的上下文
    this._context = context;
    this._mountOrder = nextMountID++;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;
    var publicProps = this._currentElement.props;
    var publicContext = this._processContext(context);
    var Component = this._currentElement.type;
    var updateQueue = transaction.getUpdateQueue();
    var doConstruct = shouldConstruct(Component);
    //初始化公共類
    var inst = this._constructComponent(doConstruct, publicProps, publicContext,updateQueue,);
    var renderedElement;
    // 用於判斷元件是否為stateless,無狀態元件沒有狀態更新佇列,只專注渲染
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      inst = new StatelessComponent(Component);
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass;
      } else {
        this._compositeType = CompositeTypes.ImpureClass;
      }
    }
    // 這些初始化引數本應該在構造器中設定,在此設定是為了便於進行簡單的類抽象
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = updateQueue;
    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,hostParent, hostContainerInfo,transaction,context,);
    } else {
      //執行初始化掛載
      markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction,context,);
    }
    //如果存在componentDidMount,則呼叫
    if (inst.componentDidMount) {
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }
    return markup;
  },
  
  performInitialMountWithErrorHandling: function( renderedElement,  hostParent, hostContainerInfo, transaction, context, ) {
    var markup;
    var checkpoint = transaction.checkpoint();
    try {
      //捕捉錯誤,如果沒有錯誤,則初始化掛載
      markup = this.performInitialMount( renderedElement,  hostParent, hostContainerInfo, 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, hostParent, hostContainerInfo, transaction,  context, );
    }
    return markup;
  },

  performInitialMount: function( renderedElement,  hostParent, hostContainerInfo, transaction, context, ) {
    var inst = this._instance;
    var debugID = 0;
    //如果存在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();
    }
    var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    // 得到 _currentElement 對應的 component 類例項
    var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY );
    this._renderedComponent = child;
    //render遞迴渲染
    var markup = ReactReconciler.mountComponent( child,  transaction,  hostParent,  hostContainerInfo,
                                                  this._processChildContext(context),  debugID,);
    return markup;
  },

複製程式碼

生命週期-修改階段(RECEIVE_PROPS)


  1. updateComponent 負責管理生命週期中的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和 componentDidUpdate。
  2. 首先通過 updateComponent 更新元件,如果前後元素不一致,說明需要進行元件更新。
  3. 若存在 componentWillReceiveProps,則執行。如果此時在 componentWillReceiveProps 中呼叫 setState,是不會觸發 re-render 的,而是會進行 state 合併。 且在 componentWillReceiveProps、shouldComponentUpdate 和 componentWillUpdate 中也還是無法獲取到更新後的 this.state, 即此時訪問的 this.state 仍然是未更新的資料,需要設定 inst.state = nextState 後才可以,因此只有在 render 和 componentDidUpdate 中才能獲取到更新後的 this.state。
  4. 呼叫 shouldComponentUpdate 判斷是否需要進行元件更新,如果存在 componentWillUpdate,則執行。updateComponent 本質上也是通過遞迴渲染內容的,由於遞迴的特性,父元件的 componentWillUpdate 是在其子元件的 componentWillUpdate 之前呼叫的,而父元件的 componentDidUpdate也是在其子元件的 componentDidUpdate 之後呼叫的。
  5. 當渲染完成之後,若存在 componentDidUpdate,則觸發,這就解釋了 componentWillReceiveProps、componentWillUpdate、render、componentDidUpdate 它們之間的執行順序。

  // receiveComponent 是通過呼叫 updateComponent 進行元件更新的
  receiveComponent: function(nextElement, transaction, nextContext) {
    var prevElement = this._currentElement;
    var prevContext = this._context;
    this._pendingElement = null;
    this.updateComponent(transaction,prevElement,nextElement,prevContext,nextContext,);
  },
  
  
    updateComponent: function(transaction,prevParentElement,nextParentElement,prevUnmaskedContext,nextUnmaskedContext,) {
    var inst = this._instance;
    var willReceive = false;
    var nextContext;

    // 上下文是否改變
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

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

    // 新舊屬性不同
    if (prevParentElement !== nextParentElement) {
      willReceive = true;
    }
    //新舊屬性不同,並且存在componentWillReceiveProps,就執行componentWillReceiveProps()
    if (willReceive && inst.componentWillReceiveProps) {
      inst.componentWillReceiveProps(nextProps, nextContext);
    }
    //將新的state合併到更新佇列中,此時的nextState是最新的state
    var nextState = this._processPendingState(nextProps, nextContext);
    var shouldUpdate = true;
    //根據更新佇列和shouldComponentUpdate的狀態來判斷是否需要更新元件
    if (!this._pendingForceUpdate) {
      if (inst.shouldComponentUpdate) {
        shouldUpdate = inst.shouldComponentUpdate(nextProps,nextState,nextContext,);
      } else {
        if (this._compositeType === CompositeTypes.PureClass) {
          shouldUpdate =!shallowEqual(prevProps, nextProps) ||!shallowEqual(inst.state, nextState);
        }
      }
    }

    this._updateBatchNumber = null;
    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;
    }
  },
  //在componentShouldUpdate()方法執行前呼叫了:就是說在呼叫之前將所有state操作合併
    _processPendingState: function(props, context) {
      var inst = this._instance;
      var queue = this._pendingStateQueue;
      var replace = this._pendingReplaceState;
      this._pendingReplaceState = false;
      this._pendingStateQueue = null;
      //如果佇列為null,返回原state
      if (!queue) {
        return inst.state;
      }
      //如果佇列中有一個更新就返回這個更新值
      if (replace && queue.length === 1) {
        return queue[0];
      }
      //如果佇列中有多個更新,就將他們合併
      var nextState = Object.assign({}, replace ? queue[0] : inst.state);
      for (var i = replace ? 1 : 0; i < queue.length; i++) {
        var partial = queue[i];
        Object.assign(
          nextState,
          typeof partial === 'function'
            ? partial.call(inst, nextState, props, context)
            : partial,
        );
      }
  
      return nextState;
    },
  
    /**
   * 當元件需要更新時,呼叫
   */
  _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.props,this.state,this.context
    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;
    //渲染元件
    this._updateRenderedComponent(transaction, unmaskedContext);
    //當元件完成更新後,如果存在ComponentDidUpdate,則呼叫
    if (hasComponentDidUpdate) {
      transaction.getReactMountReady().enqueue(inst.componentDidUpdate.bind(inst,prevProps, prevState,prevContext,),inst,);
    }
  },
  
    /**
   * 呼叫render渲染元件
   */
  _updateRenderedComponent: function(transaction, context) {
    var prevComponentInstance = this._renderedComponent;
    var prevRenderedElement = prevComponentInstance._currentElement;
    var nextRenderedElement = this._renderValidatedComponent();

    var debugID = 0;
    //如果需要更新,則呼叫ReactReconciler.receiveComponent()繼續更新元件
    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      ReactReconciler.receiveComponent(prevComponentInstance,nextRenderedElement,transaction,this._processChildContext(context),);
    } else {
      //如果不需要更新,則渲染元件
      var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
      ReactReconciler.unmountComponent(prevComponentInstance, false);

      var nodeType = ReactNodeTypes.getType(nextRenderedElement);
      this._renderedNodeType = nodeType;
      //得到 nextRenderedElement 對應的 component 類例項
      var child = this._instantiateReactComponent( nextRenderedElement,nodeType !== ReactNodeTypes.EMPTY );
      this._renderedComponent = child;
      //使用render遞迴渲染
      var nextMarkup = ReactReconciler.mountComponent(child,transaction,this._hostParent,this._hostContainerInfo,this._processChildContext(context),debugID,);

      this._replaceNodeWithMarkup(oldHostNode, nextMarkup,prevComponentInstance,);
    }
  },
複製程式碼

生命週期-解除安裝階段(UNMOUNTING)


  1. unmountComponent 負責管理生命週期中的 componentWillUnmount。
  2. 如果存在 componentWillUnmount,則執行並重置所有相關引數、更新佇列以及更新狀態,如果此時在 componentWillUnmount 中呼叫 setState,是不會觸發 re-render 的, 這是因為所有更新佇列和更新狀態都被重置為 null,並清除了公共類,完成了元件解除安裝操作

  unmountComponent: function(safely) {
    if (!this._renderedComponent) {
      return;
    }

    var inst = this._instance;
    //如果存在componentWillUnmount,則呼叫
    if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
      inst._calledComponentWillUnmount = true;

      if (safely) {
        var name = this.getName() + '.componentWillUnmount()';
        ReactErrorUtils.invokeGuardedCallback(
          name,
          inst.componentWillUnmount.bind(inst),
        );
      } else {
          inst.componentWillUnmount();
      }
    }
    //如果元件已經渲染,則對元件進行componentWillUnmount()
    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 = 0;
    this._topLevelWrapper = null;
    //刪除這個例項
    ReactInstanceMap.remove(inst);
  },
複製程式碼

無狀態元件


  1. 無狀態元件只有一個render方法,並沒有元件類的例項化過程,也沒有例項返回
  2. 無狀態元件沒有狀態,沒有生命週期,就是接受props渲染生成dom結構是一個純粹為渲染而生的元件,
//無狀態元件只有一個render方法
function StatelessComponent(Component) {}
StatelessComponent.prototype.render = function() {
  var Component = ReactInstanceMap.get(this)._currentElement.type;
  //沒有state狀態
  var element = Component(this.props, this.context, this.updater);
  return element;
};
複製程式碼

相關文章