React@15.6.2源码解析-挂载阶段完成---从 ReactDOM.render 到页面渲染(5)

上回说到了ReactMount._renderNewRootComponent中调用了ReactUpdates.batchedUpdates方法,实质上调用的是ReactDefaultBatchingStrategy.batchedUpdates,然后就讲了一下Transaction。那么现在回到流程上来,继续往下走,现在Transaction也了解了,那么剩下的就好办了。

回到流程来,我们来看一下ReactDefaultBatchingStrategy.batchedUpdates,首先先说一下参数

callback: batchedMountComponentIntoNode // 是在ReactMount文件里

a: componentInstance // 执行instantiateComponent得到的结果,那么当前得到的实例是ReactCompositeComponent

b: container

c: shouleReuseMarkup // false

d: context // {}

e: null
// ReactDefaultBatchingStrategy.js
batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }

首先拿到当前的isBatchingUpdates,那么肯定是 false 了啊

然后设置为 true

那么判断isBatchingUpdates 赋值的时候是 false,所以执行transaction.perform,那么这边的transaction指的是ReactDefaultBatchingStrategyTransaction

// ReactDefaultBatchingStrategy.js
var transaction = new ReactDefaultBatchingStrategyTransaction();

那么这边执行perform,根据上一篇博客讲的,会首先执行所有wrapper的initialize方法。

perform: function (method, scope, a, b, c, d, e, f) {
    !!this.isInTransaction() ? /**/
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      errorThrown = true;
      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  }

但是ReactDefaultBatchingStrategyTransaction包裹的wrapper都是空函数。

那就到了这一步ret = method.call(scope, a, b, c, d, e, f);开始执行我们传递的这个方法。也就是执行ReactMout里的batchedMountComponentIntoNode

// ReactMount.js
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
  !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
  transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}

首先从ReactUpdates.ReactReconcileTransaction.getPooled(true)拿到池子里的一个ReactReconcileTransaction实例。ReactUpdatesgetPooled在之前的博客已经做过声明了,需要的同学可以自行查看。

然后执行perform方法,这边执行的是ReactReconcileTransaction.perform

最后释放这个transaction

那么关键的地方在于这边又执行了一个Transction(ReactReconcileTransaction)。

一样的我们先记录一下传递的参数。

callback: mountComponentIntoNode // ReactMount.js里的
scope: null
a: componentInstance
b: container
c: transaction // ReactReconcileTransaction
d: shouldReuseMarkup // false
e: context // {}

那么先执行所有的initialize

// ReactReconcileTransaction.js

var SELECTION_RESTORATION = {
  /**
   * @return {Selection} Selection information.
   */
  initialize: ReactInputSelection.getSelectionInformation,
  /**/
};

var EVENT_SUPPRESSION = {
    initialize: function () {
        var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
        ReactBrowserEventEmitter.setEnabled(false);
        return currentlyEnabled;
    },
}

var ON_DOM_READY_QUEUEING = {
  initialize: function () {
    this.reactMountReady.reset();
  }
};

第三个wrapper的初始化执行this.reactMountReady.reset()实质上就是执行callbackQueue.reset,这个函数很简单就是将callbackQueue的_callbacks _contexts 都清空设为null

CallbackQueue.prototype.reset = function reset() {
    this._callbacks = null;
    this._contexts = null;
};

之后就是执行包装的这个函数mountComponentIntoNode

// ReactMount.js

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
  var markerName;
  // false
  if (ReactFeatureFlags.logTopLevelRenders) {
    var wrappedElement = wrapperInstance._currentElement.props.child;
    var type = wrappedElement.type;
    markerName = 'React mount: ' + (typeof type === 'string' ? type : type.displayName || type.name);
    console.time(markerName);
  }

  var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */
  );
  if (markerName) {
    console.timeEnd(markerName);
  }
  wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
  ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}

判断ReactFeatureFlags.logTopLevelRenders,当前值为false,接下来执行ReactReconciler.mountComponent,这个方法也是一个重点。拿到markup后,会执行ReactMount._mountImageIntoNode,这个函数开始将dom挂载在container上。从这就开始有点挂载的样子了。

这边可以看到参数中调用了一个ReactDOMContainerInfo(wrapperInstance, container),这个函数主要的作用就是封装了一下节点的信息,例如节点类型,节点的namespaceURI之类的。那么在函数调用时,首先是先调用这个函数拿到他的返回值之后作为形参然后调用原函数。

// ReactDOMContainerInfo.js

function ReactDOMContainerInfo(topLevelWrapper, node) {
  var info = {
    _topLevelWrapper: topLevelWrapper,
    _idCounter: 1,
    _ownerDocument: node ? node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument : null,
    _node: node,
    _tag: node ? node.nodeName.toLowerCase() : null,
    _namespaceURI: node ? node.namespaceURI : null
  };
  if (process.env.NODE_ENV !== 'production') {
    /**/
  }
  return info;
}

上面这个返回的info就是封装的节点信息对象。

先记录一下参数

internalInstance:wrapperInstance,这边的wrapperInstance还是上一个函数的形参,所以需要用流程图好好的记录一下参数的传递。

transaction: ReconcileTransaction

hostParent: null

hostContainerInfo: ReactDOMContainerInfo(wrapperInstance, container)

context: {}

parentDebugId: 0
// ReactReconcile.js

  mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots
  {
    if (process.env.NODE_ENV !== 'production') {
      /**/
    }
    var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
    if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    if (process.env.NODE_ENV !== 'production') {
      /**/
    }
    return markup;
  },

内部首先执行实例的mountComponent,当前传入的实例是wrapperInstance,那么这个实例是ReactCompositeComponentWrapper的一个实例。所以这边转到ReactCompositeComponent.js文件

ReactCompositeComponent

在讲解ReactCompositeComponent.mountComponent之前,先看一下React15中的生命周期。React16的生命周期和15是不一样的话,废除了几个换成了几个新的,这边就不涉及了。

那么这张图写的就很清楚了

在挂载阶段

  • 根组件的constructor
  • 根组件的componentWillMount
  • 根组件的render
    • 子组件的constructor
    • 子组件的componentWillMount
    • 子组件的render
    • 子组件的componentDidMount
  • 根组件的componentDidMount

更新阶段 同 挂在阶段

卸载阶段

  • 根组件的componentWillUnMount
  • 子组件的componentWillUnMount
  • 子组件销毁
  • 根组件销毁

那么按照我们的例子就是

  • TopLevelWrapperconstructor
  • TopLevelWrappercomponentWillMount
  • TopLevelWrapperrender
    • Appconstructor
    • AppcomponentWillMount
    • Apprender
    • AppcomponentDidMount
  • TopLevelWrappercomponentDidMount

mountComponent

// ReactCompositeComponent.js

  mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
    var _this = this;
    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();

    // Initialize the public class
    var doConstruct = shouldConstruct(Component);
    var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
    var renderedElement;
    // Support functional components
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      !(inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? /**/
      inst = new StatelessComponent(Component);
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass;
      } else {
        this._compositeType = CompositeTypes.ImpureClass;
      }
    }

    if (process.env.NODE_ENV !== 'production') {
      // This will throw later in _renderValidatedComponent, but add an early
      // warning now to help debugging
      if (inst.render == null) {
        process.env.NODE_ENV !== 'production' ? /**/
      }

      var propsMutated = inst.props !== publicProps;
      var componentName = Component.displayName || Component.name || 'Component';

      process.env.NODE_ENV !== 'production' ? /**/
    }
    // These should be set up in the constructor, but as a convenience for
    // simpler class abstractions, we set them up after the fact.
    // 这些应该在构造函数中设置,但是为了方便更简单的类抽象,我们在事后才设置它们。
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = updateQueue;

    this._instance = inst;

    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);

    if (process.env.NODE_ENV !== 'production') {
      /**/
    }

    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }
    !(typeof initialState === 'object' && !Array.isArray(initialState)) ? /**/

    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);
    }
    if (inst.componentDidMount) {
      if (process.env.NODE_ENV !== 'production') {
        transaction.getReactMountReady().enqueue(function () {
          measureLifeCyclePerf(function () {
            return inst.componentDidMount();
          }, _this._debugID, 'componentDidMount');
        });
      } else {
        transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
      }
    }
    return markup;
  },

代码有点长,那么来一块一块的看。

那么首先是给当前实例添加一些属性

    this._context = context;
    this._mountOrder = nextMountID++;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

这边呢有一个nextMountID变量,这是外部闭包的一个变量,用于记录着挂载的顺序的。

/**
 * An incrementing ID assigned to each when it is mounted. This is
 * used to enforce the order in which `ReactUpdates` updates dirty components.
 * 这用于强制执行“ReactUpdates”更新脏组件的顺序。
 * @private
 */
var nextMountID = 1;

再往下

var updateQueue = transaction.getUpdateQueue();

这边获取了当前事务的ReactUpdateQueue实例。

// Initialize the public class
    var Component = this._currentElement.type;
    var doConstruct = shouldConstruct(Component); // true

这边调用shouldConstruct函数对this._currentElement.type做一个判断,判断原型链中是否是isReactComponent属性。而当前的_currentElement是一开始的nextWrapperElement他是对TopLevelWrapper执行ReactElement方法得到的ReactElement。那么当前的type就是这边的TopLevelWrapper。那么找ReactMount.js发现,TopLevelWrapper的原型链上是有isReactComponent属性的。

shouldConstruct

// ReactCompositeComponent.js
function shouldConstruct(Component) {
  return !!(Component.prototype && Component.prototype.isReactComponent);
}

// ReactMount.js
var TopLevelWrapper = function () {
  this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};

接下来执行this._constructComponent方法。传递参数为

doConstruct: true

publicProps: this._currentElement.props // 当前为nextWrapperElement的props,存储的是type为App构造函数的ReactElement

publicContext: this._processContext(context) // {}

updateQueue: transaction.getUpdateQueue() // ReactUpdateQueue实例

_constructComponent

  _constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
    if (process.env.NODE_ENV !== 'production' && !doConstruct) {
      /**/
    } else {
      return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
    }
  },

本质上是执行的this._constructComponentWithoutOwner,参数还是原封不动的传递过去

_constructComponentWithoutOwner

  _constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
    var Component = this._currentElement.type;
    if (doConstruct) {
      if (process.env.NODE_ENV !== 'production') {
        /**/
      } else {
        return new Component(publicProps, publicContext, updateQueue);
      }
    }

    // This can still be an instance in case of factory components
    // but we'll count this as time spent rendering as the more common case.
    if (process.env.NODE_ENV !== 'production') {
      /**/
    } else {
      return Component(publicProps, publicContext, updateQueue);
    }
  },

如果这边的doConstructtrue的话,那么就会返回一个this._currentElement.type的实例,否则的话则会调用this._currentElement.type返回其返回值。这边笼统的说就是要实例化一个this._currentElement.type存储的值。

那么当前doConstruct是为true的,也就是说会返回一个TopLevelWrapper实例。

那么回到mountComponent方法中继续往下走

var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);

这边的inst现在就是TopLevelWrapper实例了。继续往下走

    // Support functional components
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      !(inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? /**/
      inst = new StatelessComponent(Component);
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass; // 1
      } else {
        this._compositeType = CompositeTypes.ImpureClass; // 0
      }
    }

第一个if根据注释猜测是函数式组件才会调用。那么当前的情况是进入到了else内部。主要操作在于给当前实例添加_compositeType对象。值是根据isPureComponent函数来决定的。

这边的CompositeTypes是外部闭包的一个对象,其实就是一个枚举。

var CompositeTypes = {
  ImpureClass: 0, // 非 PureClass
  PureClass: 1, // PureClass
  StatelessFunctional: 2 // 无状态的函数式组件
};

那么总的来说就是如果我们这个ComponentPureClass的话,_compositeType对应CompositeTypes.ImpureClass,否则的话就是CompositeTypes.PureClass。在第一篇博客讲解React的全局API时,对于PureComponent是有一个特殊的标志的isPureReactComponent

// ReactCompositeComponent.js
function isPureComponent(Component) {
  return !!(Component.prototype && Component.prototype.isPureReactComponent);
}

// react/lib/ReactBaseClass.js
function ReactPureComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactPureComponent.prototype.isPureReactComponent = true;

那么接着往下

    // These should be set up in the constructor, but as a convenience for
    // simpler class abstractions, we set them up after the fact.
    // 这些应该在构造函数中设置,但是为了方便更简单的类抽象,我们在事后才设置它们。
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = updateQueue;

inst附加属性,官方注释的解释是这些属性本应在构造函数中设置但是为了更简单的类抽象,才在这边进行附加。

this._instance = inst;

给当前实例附加_instance属性,值为inst

    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);

将当前的inst存储起来。ReactInstanceMap对象做的事情就是一件事,维护来自面向公共的有状态实例(键)和内部表示(值)的映射。这允许公共方法接受面向用户的实例作为参数,并将它们映射回内部方法。

说白了给key值附加一个属性_reactInternalInstance,值为传入的value。

那么这边就是给inst附加一个_reactInternalInstance属性,值为this也就是当前ReactCompositeComponent实例。

    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }

获取初始的state,不存在的话初始化为null

走这开始就可以把生命周期给连上了。这一步对应着我们在构造函数里写的this.state = {},例子如下

class App extends React.Component {
    constructor(props) {
        super(props);
        // 对应着这里
        this.state = {
            name: 'Hello World'
        }
    }
    /**/
}

但是我们当前的instTopLevelWrapper显然没有这么个东西。

    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);
    }

这边对inst.unstable_handleError判断之后执行初始化的挂载,那么这边显然没有这个属性。这个performInitialMount方法就对应着我们的componentWillMount生命周期。

performInitialMount

  performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
    var inst = this._instance;

    var debugID = 0;
    if (process.env.NODE_ENV !== 'production') {
      debugID = this._debugID;
    }

    if (inst.componentWillMount) {
      if (process.env.NODE_ENV !== 'production') {
        /**/
      } else {
        inst.componentWillMount();
      }
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingStateQueue` without triggering a re-render.
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }
    // If not a stateless component, we now render
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

    var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
    );
    this._renderedComponent = child;

    var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
    if (process.env.NODE_ENV !== 'production') {
      /**/
    }

    return markup;
  },

那么上面也提到了这个函数相当于componentWillMount,执行初始化的挂载。

    if (inst.componentWillMount) {
      if (process.env.NODE_ENV !== 'production') {
        measureLifeCyclePerf(function () {
          return inst.componentWillMount();
        }, debugID, 'componentWillMount');
      } else {
        inst.componentWillMount();
      }
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingStateQueue` without triggering a re-render.
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }

那么这边判断componentWillMount是否存在,存在的话就执行,在执行期间执行的setState将会加入到_pendingStateQueue,这边需要注意的是,在componentWillMount期间执行的setState并不会引发重新的render

执行完之后判断_pendingStateQueue是否存在,也就是判断在执行期间是否调用了setState,如果有的话会将这个队列中的state进行比较,得到一个最终的state。

_processPendingState

  _processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }

    return nextState;
  }

通过Object.assign一个一个的覆盖前者得到一个最终的state。得到最终的一个state之后赋值给inst.state,之后就是判断传入的这个renderedElement是否是undefined了。

    // If not a stateless component, we now render
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

根据这个英文注释了解到如果是一个无状态组件组件,那么就渲染他,突然发现我们的TopLevelWrapper就是一个无状态的组件,那么当前就是undefined,ok,渲染他。

_renderValidatedComponent

  _renderValidatedComponent: function () {
    var renderedElement;
    if (process.env.NODE_ENV !== 'production' || this._compositeType !== CompositeTypes.StatelessFunctional) {
      ReactCurrentOwner.current = this;
      try {
        renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
      } finally {
        ReactCurrentOwner.current = null;
      }
    } else {
      renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
    }
    !(
    // TODO: An `isValidNode` function would probably be more appropriate
    renderedElement === null || renderedElement === false || React.isValidElement(renderedElement)) ? process.env.NODE_ENV !== 'production' ? /**/
    return renderedElement;
  },

那么实际上就是执行_renderValidatedComponentWithoutOwnerOrContext函数并将返回值返回出来。

_renderValidatedComponentWithoutOwnerOrContext

  _renderValidatedComponentWithoutOwnerOrContext: function () {
    var inst = this._instance;
    var renderedElement;

    if (process.env.NODE_ENV !== 'production') {
      renderedElement = measureLifeCyclePerf(function () {
        return inst.render();
      }, this._debugID, 'render');
    } else {
      renderedElement = inst.render();
    }

    if (process.env.NODE_ENV !== 'production') {
      // We allow auto-mocks to proceed as if they're returning null.
      if (renderedElement === undefined && inst.render._isMockFunction) {
        // This is probably bad practice. Consider warning here and
        // deprecating this convenience.
        renderedElement = null;
      }
    }

    return renderedElement;
  },

这个就简单了,直接执行this._instance.render,那么当前的_instanceTopLevelWrapper实例,他的render方法就是返回他的props.child

// ReactMount.js
TopLevelWrapper.prototype.render = function () {
  return this.props.child;
};

那么这边的child就是我们的React.createElement(App)的结果。将返回值依次返回那么就回到了performInitialMount函数。

// ReactCompositeComponent.performInitialMount
    // If not a stateless component, we now render
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

也就是回到了这,如果有童鞋打断点的话,那么可以看

Chrome 里面的Call Stack看整个流程的函数调用栈。

那么继续往下走

var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;

这边给当前实例附加一个_renderedNodeType属性,调用了一个ReactNodeTypes.getType()方法,

// ReactNodeType.js
var ReactNodeTypes = {
  HOST: 0,
  COMPOSITE: 1,
  EMPTY: 2,

  getType: function (node) {
    if (node === null || node === false) {
      return ReactNodeTypes.EMPTY;
    } else if (React.isValidElement(node)) {
      if (typeof node.type === 'function') {
        return ReactNodeTypes.COMPOSITE;
      } else {
        return ReactNodeTypes.HOST;
      }
    }
    !false ? process.env.NODE_ENV !== 'production' ? /**/
  }
};

根据node型和node.type进行判断,主要目的就是判断当前的node是哪一种类型的组件,这个操作在instantiateReactComponent中有类似的操作。继续往下

var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY);

调用_instantiateReactComponent函数,该函数是在实例化当前实例时进行赋值的也就是instantiateReactComponent.js里面的方法,重要目的就是创建一个ReactComponent实例。

具体的操作就不去看了之前已经介绍过,那么我们需要注意的是这边传入的renderedElement参数是我们的类型为AppReactElement,那么根据之前的介绍,这个方法会返回一个ReactCompositeComponent的实例。

那么继续往下

this._renderedComponent = child;

附加_renderedComponent属性值为上一步得到的child

继续往下

var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);

执行ReactReconcile.mountComponent,那么又回到了ReactCompositeComponent.mountComponent函数,只不过this指向了这个child

那么我们来看一下当前的调用栈

再来看一下this指向

可以看到this._currentElement是type为App的ReactElement。

那么还是一样的附加几个属性,这边的_mountOrder就是2

this._mountOrder = nextMountID++;

再往下经过一系列平淡无奇的操作之后到了这

var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);

这边需要注意的是实例化App时,传入了这个updateQueue,而App的构造函数内部调用了super(props)调用了一次父类的构造函数也就是React.Component的构造函数,

// ReactBaseClasses.js
function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

那么这边只传递了props,其余两个变量都是undefined,那么重点在这

this.updater = updater || ReactNoopUpdateQueue;

这边的this.updater赋值的是ReactNoopUpdateQueue对象。

那么我们来看一下这个App的实例有哪些属性。

原型链上的几个方法是通过babel打进去的。

在往下又经历了一系列的操作到了这

    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = updateQueue;

重点在于最后一句,对inst.updater重新赋值了,一开始实例化App的时候用的是缺省的ReactNoopUpdateQueue,那么这边是重新改成ReactUpdateQueue,我估计是防止有人在执行super()的时候,传了不止props一个参数,这边做一个重新赋值保证updater一定是ReactUpdateQueue.

再次经历一系列的操作之后进入了performInitialMount函数,开始执行componentWillMount方法,但是我们的componentWillMount方法里调用了setState,我们来看一下React对setState的处理。

ReactComponent.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? /**/
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

调用this.updater.enqueueSetState方法,那么我们这边的updater上面也说了是一个ReactUpdateQueue,来看一下这个对象里的方法。

//ReactUpdateQueue.js
  enqueueSetState: function (publicInstance, partialState) {
    if (process.env.NODE_ENV !== 'production') {
      /**/
    }

    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

    if (!internalInstance) {
      return;
    }

    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

那么这边调用getInternalInstanceReadyForUpdate方法从ReactInstanceMap�中查找对应的key值,主要就是查找对应的实例是否存在,不存在的话就报错退出。

之后就是将新的state加入到实例的_pendingStateQueue里面,上文也说了执行完componentWillMount之后会检查_pendingStateQueue得到最新的state之后对实例的state进行赋值。

之后调用enqueueUpdate方法

// ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}

实际上就是执行ReactUpdates.enqueueUpdate,ok,我们跳到这里看一下发生了什么。

function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent's
  // _renderValidatedComponent) assume that calls to render aren't nested;
  // verify that that's the case. (This is called by each top-level update
  // function, like setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)
  // 我们代码的各个部分(比如ReactCompositeComponent的_renderValidatedComponent)
  // 假设render调用不是嵌套的;验证一下。(这是由每个顶级更新函数调用,如setState、forceUpdate等;
  //   顶级组件的创建和销毁在ReactMount中受到保护。)

  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

首先确保依赖注入,之后判断batchingStrategy.isBatchingUpdates变量,那么当前值还是true的。所以下面就将该组件push进dirtyComponents,并且给组件添加一个_updateBatchNumber值,这个updateBatchNumber是外部闭包的一个变量。

一路return回到了performInitialMount函数,开始检测_pendingStateQueue,这边按照我们的示例是有一个的,那么就计算出最新的state,那么可以发现React是进行批处理的。

之后因为renderedElementundefined开始执行render,就到了_renderValidatedComponent函数,继而又到了_renderValidatedComponentWithoutOwnerOrContext函数,执行实例的render方法,我们来看一下App的render方法

    render() {
        console.log('render');
        return (
            <div>
                { this.state.name }
            </div>
        )
    }

render函数打印一句话然后返回一个DOM节点。然后一路返回到了performInitialMount方法。

一路平淡无奇到了这

var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY);

那么这会的renderedElement就是我们render返回出来的那个DOM节点,只不过被babel调用了React.createElement解析成了一个ReactElement。我们来看一下当前的这个ReactElement

那么这边调用_instantiateReactComponent实际上也就是instantiateReactComponent方法,这边因为node.type是一个字符串,那么会调用ReactHostComponent.createInternalComponent创建一个ReactHostComponent实例,实际上也就是ReactDOMComponent实例。

拿到了child之后,又开始对child执行挂载,这会执行的就是ReactDOMComponent.mountComponent

一系列操作完之后,回到了ReactCompositeComponent.mountComponnet中继续往下执行。到了这也就是App以下的都挂载完了,开始执行App的componentDidMount

    if (inst.componentDidMount) {
      if (process.env.NODE_ENV !== 'production') {
        /**/
      } else {
        transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
      }
    }

这边直接是把componentDidMount方法压入ReactReconcileTransaction的callbackQueue队列中。因为这边transaction.getReactMountReady()是返回的一个callbackQueue。

到此App的componentDidMount之前的都已经执行完了,回到了ReactReconcile.mountComponent.

mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots
  {
    if (process.env.NODE_ENV !== 'production') {
      /**/
    }
    // 走这开始调用实例的挂载
    var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
    if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    if (process.env.NODE_ENV !== 'production') {
      if (internalInstance._debugID !== 0) {
        ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID);
      }
    }
    return markup;
  }

那么到现在我们可以理会到了ReactReconcile的作用了,他内部的mountComponent是所有实例挂载的入口,在ReactReconcile内部才会开始调用实例自己的mountComponent。而Reconcile调度也就是这个意思。

那么再执行完实例自己的mountComponent之后,判断实例是否具有ref属性,如果有的话会将一个函数名为attachRefs,调用上下文为当前传入的实例(internalInstance)压入到事务的callbackQueue中。那么之所以要将函数和上下文都一起保存是因为是要确保回调函数的内部this指向。

那么之后就是一路的return,回到了mountComponentIntoNode,开始执行ReactMount._mountImageIntoNode方法

  _mountImageIntoNode: function (markup, container, instance, shouldReuseMarkup, transaction) {
    /**/
    if (shouldReuseMarkup) {
      var rootElement = getReactRootElementInContainer(container);
      if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
        ReactDOMComponentTree.precacheNode(instance, rootElement);
        return;
      } else {
        var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
        rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);

        var rootMarkup = rootElement.outerHTML;
        rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum);

        var normalizedMarkup = markup;
        if (process.env.NODE_ENV !== 'production') {
         /**/
        }

        var diffIndex = firstDifferenceIndex(normalizedMarkup, rootMarkup);
        var difference = ' (client) ' + normalizedMarkup.substring(diffIndex - 20, diffIndex + 20) + '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);
         /**/
    }
    /**/
    if (transaction.useCreateElement) {
      // 清空容器子节点,换行符也算一个childNode,lastChild,firstChild是取的childNodes,不是children,换行符也算一个childNode
      while (container.lastChild) {
        container.removeChild(container.lastChild);
      }
      DOMLazyTree.insertTreeBefore(container, markup, null);
    } else {
      setInnerHTML(container, markup);
      ReactDOMComponentTree.precacheNode(instance, container.firstChild);
    }
    if (process.env.NODE_ENV !== 'production') {
      /**/
    }
  }

首先对shouleReuseMarkup做判断,当前为false,往下就是判断transaction.useCreateElement,那么这个useCreateElement是在实例化ReactReconcileTransaction时,传给构造函数的一个参数,当前为true。ok,进入这个if内部

一个while清空container内部的所有子节点

打断点走到这可以看出root节点里的子节点都被清空了,然后开始执行DOMLazyTree.insertTreeBefore方法,开始将虚拟DOM插入到真实DOM中。

var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function (parentNode, tree, referenceNode) {
  // DocumentFragments aren't actually part of the DOM after insertion so
  // appending children won't update the DOM. We need to ensure the fragment
  // is properly populated first, breaking out of our lazy approach for just
  // this level. Also, some <object> plugins (like Flash Player) will read
  // <param> nodes immediately upon insertion into the DOM, so <object>
  // must also be populated prior to insertion into the DOM.
  // 文档片段在插入之后实际上不是DOM的一部分,所以附加的子元素不会更新DOM。
  // 我们需要确保首先正确填充了片段,从而打破只针对这个级别的惰性方法。
  // 此外,一些<object>插件(如Flash Player)在插入到DOM时将立即读取<param>节点,因此在插入到DOM之前还必须填充<object>。
  if (tree.node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE || tree.node.nodeType === ELEMENT_NODE_TYPE && tree.node.nodeName.toLowerCase() === 'object' && (tree.node.namespaceURI == null || tree.node.namespaceURI === DOMNamespaces.html)) {
    insertTreeChildren(tree);
    parentNode.insertBefore(tree.node, referenceNode);
  } else {
    parentNode.insertBefore(tree.node, referenceNode);
    insertTreeChildren(tree);
  }
});

tree.node.nodeType做判断,当前进入else代码块,只看代码的话会发现if和else的代码块只是顺序不一样。那么接下来就是DOM操作了,将tree.node插入到parent的子节点中,这边的referenceNodenull,表明在parentNode的子节点末尾插。

那么这个时候我们发现这个时候节点已经插进去了

接下来调用`insertTreeChildren方法

function insertTreeChildren(tree) {
  if (!enableLazy) {
    return;
  }
  var node = tree.node;
  var children = tree.children;
  if (children.length) {
    for (var i = 0; i < children.length; i++) {
      insertTreeBefore(node, children[i], null);
    }
  } else if (tree.html != null) {
    setInnerHTML(node, tree.html);
  } else if (tree.text != null) {
    setTextContent(node, tree.text);
  }
}

这边用到了一个变量enableLazy,这是根据不同的浏览器来判定的。理由在源码的英文注释中给出了

In IE (8-11) and Edge, appending nodes with no children is dramatically
faster than appending a full subtree, so we essentially queue up the
.appendChild calls here and apply them so each node is added to its parent
before any children are added.

In other browsers, doing so is slower or neutral compared to the other order
(in Firefox, twice as slow) so we only do this inversion in IE.

大致意思就是在IE浏览器和Edge浏览器(Microsoft Edge)中,添加没有子节点的节点比直接添加完整的DOM树要快得多。

在其他浏览器中,这样做比另一种顺序(在Firefox中,是两倍慢)更慢或更中立,所以我们只在IE中做这种倒置。

然后就对浏览器做了一个判断

var enableLazy = typeof document !== 'undefined' && typeof document.documentMode === 'number' || typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string' && /\bEdge\/\d/.test(navigator.userAgent);

如果是IE的话那么enableLazy就是true,那么当前是用的Chrome。所以这个方法就直接return了。这一块做的就是对IE浏览器做不同的插入操作。IE真的是前端的。。。。。

那么到这整个ReactReconcileTransaction.perform里的callback就都执行完了,虚拟节点已经转换为真实的DOM节点完成了挂载,但是我们还没有执行componentDidMount生命周期方法。

之前也看到这个方法是添加到了事务的callbackQueue队列中了,事实上,这个生命周期会在事务关闭的时候被执行,别忘了�事务在关闭的时候会执行所有的close的。

整个流程已经走到了这。下面就开始执行closeAll方法执行外层包裹的wrapper的close方法了。

// ReactReconcileTransaction.js
var SELECTION_RESTORATION = {
  /**/
  /**
   * @param {Selection} sel Selection information returned from `initialize`.
   */
  close: ReactInputSelection.restoreSelection
};

var EVENT_SUPPRESSION = {
  /**/
  /**
   * @param {boolean} previouslyEnabled Enabled status of
   *   `ReactBrowserEventEmitter` before the reconciliation occurred. `close`
   *   restores the previous value.
   */
  close: function (previouslyEnabled) {
    ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
  }
};

var ON_DOM_READY_QUEUEING = {
  /**/
  /**
   * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
   */
  close: function () {
    this.reactMountReady.notifyAll();
  }
};

那么前两个close我们就不关注了,来看第三个close,执行reactMountReady.notifyAll()方法。

那么这个reactMountReady本质上是一个callbackQueue,而notifyAll方法就是来执行收集到了所有的回调,并通过call方法将回调函数的this指向到对应的context

  CallbackQueue.prototype.notifyAll = function notifyAll() {
    var callbacks = this._callbacks;
    var contexts = this._contexts;
    var arg = this._arg;
    if (callbacks && contexts) {
      !(callbacks.length === contexts.length) ? process.env.NODE_ENV !== 'production' ? /**/
      this._callbacks = null;
      this._contexts = null;
      for (var i = 0; i < callbacks.length; i++) {
        callbacks[i].call(contexts[i], arg);
      }
      callbacks.length = 0;
      contexts.length = 0;
    }
  };

那么目前收集到的回调只有一个componentDidMount,我们的示例中,在这个生命周期函数中打印了一句话调用了一次setState,那么根据上面说的setState会将组件标记为dirtyComponent并将新的state添加进实例的_pendingStateQueue

调用完所有收集到的回调之后就清空_callback_context两个数组。

执行完ReactReconcileTransaction所有的close之后,将wrapperInitData清空,在事务的最后也就是finally代码快,将事务正在执行事务的标记变量记为false

    finally {
      try {
        if (errorThrown) {
          // If `method` throws, prefer to show that stack trace over any thrown
          // by invoking `closeAll`.
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          // Since `method` didn't throw, we don't want to silence the exception
          // here.
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }

那么整个调度事务到这就都执行完了。那么到这整个流程就回到了batchedMountComponentIntoNode方法,接下来就是需要调用ReactUpdates.ReactReconcileTransaction.release释放这个调度事务。

// ReactMount.js
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
  !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
  transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
  ReactUpdates.ReactReconcileTransaction.release(transaction);
}

那么到这整个batchedMountComponentIntoNode方法就算执行完了,但是这个方法是ReactDefaultBatchingStrategyTransaction事务的回调。根据事务的规矩,执行完回调就需要执行close了。

// ReactDefaultBatchingStrategy.js
var RESET_BATCHED_UPDATES = {
  /**/
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

那么第一个就不说了,设置一下isBatchingUpdates,第二个才是关键,调用了ReactUpdates.flushBatchedUpdates刷新更新队列。下面的东西就是更新阶段的事情了,因为我们在挂载阶段的两个生命周期调用了setState,当前dirtyComponents中包含两项,这个flushBatchedUpdates就是要刷新dirtyComponents来完成更新。更新阶段的事下回再说。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容