问题如题,虽然之前也有过这个疑惑,但一直就放在那。直到最近看Redux相关的文章,看到这么一段代码:

// CONTAINER
export const view = connect(
    state => ({count: state})
)(
    ({name, count, dispatch}) => <div>
        <p>{name}:
            {count}
            <button onClick={e => dispatch(decrement())}>-</button>
            <button onClick={e => dispatch(increment())}>+</button>
        </p>
    </div>
)

其中connect函数最后面的输入是一个React组件对象,在这里它其实就是一个很简单的函数返回了一个DOM对象。React组件到底都支持哪些形式呢?

React源码剖析1

要看React组件支持哪些形式,其实主要是看在进行组件渲染时(将virtual DOM渲染成真实的DOM),React是怎么做的。以下内容会比较啰嗦,TL;DR,可以直接看结论

我们在渲染React组件时首先会调用ReactDOM.render,像下面这样将我们的组件作为参数输入进去:

import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App/>,
    document.getElementById('root')
);

顺着ReactDOM.render,我们来看React是怎么实现的。在ReactMount.js中,下面的nextElement就是我们输入的React组件(准确地说是被编译成ReactElement类型的对象):

_renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
  // 省略若干代码
  var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement });
  // 省略若干代码
  var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
  if (callback) {
      callback.call(component);
  }
  return component;
}

首先这个TopLevelWrapper是下面这样子的(注意它原型的render函数返回的就是我们的React组件对象),这个函数对象对应了后面的ReactElement对象的type属性。

var topLevelRootCounter = 1;
var TopLevelWrapper = function () {
  this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
if (process.env.NODE_ENV !== 'production') {
  TopLevelWrapper.displayName = 'TopLevelWrapper';
}
TopLevelWrapper.prototype.render = function () {
  return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;

然后来看React.createElement,它最后会返回一个ReactElement对象,其中我们的React组件就是输入参数的props.child

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };
  // 省略若干代码
  return element;
}

可以看到到此为止,React组件的render函数并没有被调用,它所做的只是把组件的一些相关的信息提取了出来。

现在来看提取了这些信息的ReactElement对象的后续处理,即ReactMount._renderNewRootComponent函数(这里输入的nextElement就是包含了我们React组件的对象):

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
  // 省略若干代码
  var componentInstance = instantiateReactComponent(nextElement, false);

  // The initial render is synchronous but any updates that happen during
  // rendering, in componentWillMount or componentDidMount, will be batched
  // according to the current batching strategy.

  ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
  // 省略若干代码
  return componentInstance;
}

继续追instantiateReactComponent函数,看它的函数注释,我感觉我们找对地方了。这里我把整个函数都摘出来了:

/**
 * Given a ReactNode, create an instance that will actually be mounted.
 *
 * @param {ReactNode} node
 * @param {boolean} shouldHaveDebugID
 * @return {object} A new instance of the element's constructor.
 * @protected
 */
function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;

  if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    var element = node;
    var type = element.type;
    if (typeof type !== 'function' && typeof type !== 'string') {
      var info = '';
      if (process.env.NODE_ENV !== 'production') {
        if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
          info += ' You likely forgot to export your component from the file ' + 'it\'s defined in.';
        }
      }
      info += getDeclarationErrorAddendum(element._owner);
      !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s', type == null ? type : typeof type, info) : _prodInvariant('130', type == null ? type : typeof type, info) : void 0;
    }

    // Special case string values
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      // This is temporarily available for custom components that are not string
      // representations. I.e. ART. Once those are updated to use the string
      // representation, we can drop this code path.
      instance = new element.type(element);

      // We renamed this. Allow the old name for compat. :(
      if (!instance.getHostNode) {
        instance.getHostNode = instance.getNativeNode;
      }
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactHostComponent.createInstanceForText(node);
  } else {
    !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Encountered invalid React node of type %s', typeof node) : _prodInvariant('131', typeof node) : void 0;
  }

  if (process.env.NODE_ENV !== 'production') {
    process.env.NODE_ENV !== 'production' ? warning(typeof instance.mountComponent === 'function' && typeof instance.receiveComponent === 'function' && typeof instance.getHostNode === 'function' && typeof instance.unmountComponent === 'function', 'Only React Components can be mounted.') : void 0;
  }

  // These two fields are used by the DOM and ART diffing algorithms
  // respectively. Instead of using expandos on components, we should be
  // storing the state needed by the diffing algorithms elsewhere.
  instance._mountIndex = 0;
  instance._mountImage = null;

  if (process.env.NODE_ENV !== 'production') {
    instance._debugID = shouldHaveDebugID ? getNextDebugID() : 0;
  }

  // Internal instances should fully constructed at this point, so they should
  // not get any new fields added to them at this point.
  if (process.env.NODE_ENV !== 'production') {
    if (Object.preventExtensions) {
      Object.preventExtensions(instance);
    }
  }

  return instance;
}

以上,可以看到根据ReactElement对象类型的不同,会生成不同的对象,而这里我们从上面一路走下来得到的ReactElement对象大概是长这样的:

var node = {
  type: TopLevelWrapper, // A function
  props: {
    child: ourReactComponent, // input React component
  }
  // 省略若干属性
}

所以,我们只需要关注这么一句就可以了:

instance = new ReactCompositeComponentWrapper(element);

那么就看ReactCompositeComponentWrapper的定义:

var ReactCompositeComponentWrapper = function (element) {
  this.construct(element);
};
_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, {
  _instantiateReactComponent: instantiateReactComponent
});

这里的_assign方法可以看作是Object.assign的polyfill的函数,所以this.construct函数实际调用的是ReactCompositeComponent.construct函数。

var ReactCompositeComponent = {

  /**
   * Base constructor for all composite component.
   *
   * @param {ReactElement} element
   * @final
   * @internal
   */
  construct: function (element) {
    this._currentElement = element;
    this._rootNodeID = 0;
    this._compositeType = null;
    this._instance = null;
    this._hostParent = null;
    this._hostContainerInfo = null;

    // See ReactUpdateQueue
    this._updateBatchNumber = null;
    this._pendingElement = null;
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    this._renderedNodeType = null;
    this._renderedComponent = null;
    this._context = null;
    this._mountOrder = 0;
    this._topLevelWrapper = null;

    // See ReactUpdates and ReactUpdateQueue.
    this._pendingCallbacks = null;

    // ComponentWillUnmount shall only be called once
    this._calledComponentWillUnmount = false;

    if (process.env.NODE_ENV !== 'production') {
      this._warnedAboutRefsInRender = false;
    }
  },
  // 省略其他属性
}

可以看到,其实这里几乎什么也没做,就是把我们的ReactElement对象作为_currentElement属性放在了新生成的这个对象中。所以,直到这里,我们定义的React组件还没有被渲染出来。

那么回到之前的ReactMount._renderNewRootComponent函数中,得到了上面的对象后,还剩执行了这么一句话(其中componentInstance即为上面生成的对象):

ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);

其中batchedUpdates函数:

function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

但当我们想看batchingStrategy的定义时,发现:

var batchingStrategy = null;

也就是说这个batchingStrategy是在别的地方初始化的,大致找了一下,通过ReactUpdates.injection.injectBatchingStrategy函数可以初始化它,这个函数会在ReactServerRendering.js中被调用,并且会把ReactDefaultBatchingStrategy作为默认的对象初始化它。

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  /**
   * Call the provided function in a context within which calls to `setState`
   * and friends are batched such that components aren't updated unnecessarily.
   */
  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);
    }
  }
};

transaction.perform:

perform: function (method, scope, a, b, c, d, e, f) {
    !!this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.perform(...): Cannot initialize a transaction when there is already an outstanding transaction.') : _prodInvariant('27') : void 0;
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      // Catching errors makes debugging more difficult, so we start with
      // errorThrown set to true before setting it to false after calling
      // close -- if it's still set to true in the finally block, it means
      // one of these calls threw.
      errorThrown = true;
      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = 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;
      }
    }
    return ret;
  },

搞了半天,其实就是调用了之前的batchedMountComponentIntoNode

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

这里又绕了一下,调用的是mountComponentIntoNode:

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
  var markerName;
  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);
}

注意这里的wrapperInstance就是我们之前得到的包含了我们的React组件的那个对象,wrapperInstance._currentElement.props.child就是我们的React组件。

再看ReactReconciler.mountComponent函数,从函数定义来看,它的返回值是一个markup的string,也就是渲染后我们从网页上看到的我们的React组件的样子。

mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID // 0 in production and for roots
  ) {
    // 省略debug用代码
    var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
    if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    // 省略debug用代码
    return markup;
  },

注意到这里调用了之前得到的包裹了我们React组件的对象的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' ? invariant(false, '%s(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.', Component.displayName || Component.name || 'Component') : _prodInvariant('105', Component.displayName || Component.name || 'Component') : void 0;
      inst = new StatelessComponent(Component);
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass;
      } else {
        this._compositeType = CompositeTypes.ImpureClass;
      }
    }

    // 省略debug用代码

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

    // 省略debug用代码
  
    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }
    !(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.state: must be set to an object or null', this.getName() || 'ReactCompositeComponent') : _prodInvariant('106', this.getName() || 'ReactCompositeComponent') : void 0;

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

这里有几点值得注意:

  • React Support functional components. 这点注释上就有,当判断出我们的React组件并不符合React标准的组件时,就会被当做是函数组件,此时renderedElement = inst;,并且它的组件类型被标记为了StatelessFunctional

  • 当我们的React组件符合标准时,renderedElementundefined;此时组件类型标被记为PureClassImpureClass

  • 最关键的一句在这里:

    markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
    
  • 渲染成markup后,会调用componentDidMount(如果有这个方法的话):

    transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    

那么来看最关键的那句中的函数:

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

  // 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);
  // 省略debug用代码

  return markup;
},
  • 组件的componentWillMount函数在这里被调用了(如果定义了的话);

  • 从上面可知,传递进来的renderedElement=undefined,因此在这里会重新生成这个对象:

    renderedElement = this._renderValidatedComponent();
    

    再往里面追会发现它其实就是调用了inst.render()函数得到的;

  • 还记得这个inst是什么吗?在这里是TopLevelWrapper new出来的对象,因此inst.render()得到的就是我们的React组件对象(所以是包了一层嘛);

  • 最后这里最关键的一句是:

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

    还记得ReactReconciler.mountComponent函数之前出现过么?我们之所以追踪到这里就是通过这个函数来的!这里把当前组件的子孙组件传递进去了,形成了递归,从而可以达到渲染整个DOM tree的结果(包含真实DOM和虚拟DOM)。注意这里是单数形式的child,也就是说React virtual DOM只会有一个子孙节点,即我们的React组件的render函数必须返回的是单个节点(当然这个节点可以保护多个子节点,但最外部只能有一个根节点)。


以上,对于React组件的渲染过程基本走了一遍,但在渲染React组件时通常是会夹杂一些真实的DOM的,对于这类元素,React是怎么做的呢?

对于真实的DOM,React中实例化后是类型为ReactDOMComponent的对象,它当然也有一个mountComponent的函数作为属性(其实就类似多态啦),还记得之前的ReactReconciler.mountComponent函数吗,它会去调用组件对象的这个函数:

ReactDOMComponent.Mixin = {
  mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
    this._rootNodeID = globalIdCounter++;
    this._domID = hostContainerInfo._idCounter++;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    var props = this._currentElement.props;

    // 省略一些特殊case的处理代码

    assertValidProps(this, props);

    // We create tags in the namespace of their parent container, except HTML
    // tags get no namespace.
    var namespaceURI;
    var parentTag;
    if (hostParent != null) {
      namespaceURI = hostParent._namespaceURI;
      parentTag = hostParent._tag;
    } else if (hostContainerInfo._tag) {
      namespaceURI = hostContainerInfo._namespaceURI;
      parentTag = hostContainerInfo._tag;
    }
    if (namespaceURI == null || namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
      namespaceURI = DOMNamespaces.html;
    }
    if (namespaceURI === DOMNamespaces.html) {
      if (this._tag === 'svg') {
        namespaceURI = DOMNamespaces.svg;
      } else if (this._tag === 'math') {
        namespaceURI = DOMNamespaces.mathml;
      }
    }
    this._namespaceURI = namespaceURI;

    // 省略一些debug用代码

    var mountImage;
    if (transaction.useCreateElement) {
      var ownerDocument = hostContainerInfo._ownerDocument;
      var el;
      if (namespaceURI === DOMNamespaces.html) {
        if (this._tag === 'script') {
          // Create the script via .innerHTML so its "parser-inserted" flag is
          // set to true and it does not execute
          var div = ownerDocument.createElement('div');
          var type = this._currentElement.type;
          div.innerHTML = '<' + type + '></' + type + '>';
          el = div.removeChild(div.firstChild);
        } else if (props.is) {
          el = ownerDocument.createElement(this._currentElement.type, props.is);
        } else {
          // Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
          // See discussion in https://github.com/facebook/react/pull/6896
          // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
          el = ownerDocument.createElement(this._currentElement.type);
        }
      } else {
        el = ownerDocument.createElementNS(namespaceURI, this._currentElement.type);
      }
      ReactDOMComponentTree.precacheNode(this, el);
      this._flags |= Flags.hasCachedChildNodes;
      if (!this._hostParent) {
        DOMPropertyOperations.setAttributeForRoot(el);
      }
      this._updateDOMProperties(null, props, transaction);
      var lazyTree = DOMLazyTree(el);
      this._createInitialChildren(transaction, props, context, lazyTree);
      mountImage = lazyTree;
    } else {
      var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
      var tagContent = this._createContentMarkup(transaction, props, context);
      if (!tagContent && omittedCloseTags[this._tag]) {
        mountImage = tagOpen + '/>';
      } else {
        mountImage = tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
      }
    }
    // 省略一些特殊case的处理代码
    
    return mountImage;
  },
}

这里最关键的是这一句:

this._createInitialChildren(transaction, props, context, lazyTree);

执行完这一句会陆续把整个DOM tree渲染出来。再看它的实现:

  _createInitialChildren: function (transaction, props, context, lazyTree) {
    // Intentional use of != to avoid catching zero/false.
    var innerHTML = props.dangerouslySetInnerHTML;
    if (innerHTML != null) {
      if (innerHTML.__html != null) {
        DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
      }
    } else {
      var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null;
      var childrenToUse = contentToUse != null ? null : props.children;
      // TODO: Validate that text is allowed as a child of this node
      if (contentToUse != null) {
        // Avoid setting textContent when the text is empty. In IE11 setting
        // textContent on a text area will cause the placeholder to not
        // show within the textarea until it has been focused and blurred again.
        // https://github.com/facebook/react/issues/6731#issuecomment-254874553
        if (contentToUse !== '') {
          if (process.env.NODE_ENV !== 'production') {
            setAndValidateContentChildDev.call(this, contentToUse);
          }
          DOMLazyTree.queueText(lazyTree, contentToUse);
        }
      } else if (childrenToUse != null) {
        var mountImages = this.mountChildren(childrenToUse, transaction, context);
        for (var i = 0; i < mountImages.length; i++) {
          DOMLazyTree.queueChild(lazyTree, mountImages[i]);
        }
      }
    }
  },

这里很明显是要看这句:

var mountImages = this.mountChildren(childrenToUse, transaction, context);

它的实现在ReactMultiChild.js里:

mountChildren: function (nestedChildren, transaction, context) {
  var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
  this._renderedChildren = children;

  var mountImages = [];
  var index = 0;
  for (var name in children) {
    if (children.hasOwnProperty(name)) {
      var child = children[name];
      var selfDebugID = 0;
      if (process.env.NODE_ENV !== 'production') {
        selfDebugID = getDebugID(this);
      }
      var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID);
      child._mountIndex = index++;
      mountImages.push(mountImage);
    }
  }

  if (process.env.NODE_ENV !== 'production') {
    setChildrenForInstrumentation.call(this, children);
  }

  return mountImages;
},

看到了没?ReactReconciler.mountComponent在这里又被调用了!这次会遍历当前DOM元素对象的所有下一级子孙元素对象,并且将他们逐一渲染出来,再次形成递归。

结论###

  • 正如官方文档所言,所有的组件在编译后都会得到ReactElement类型的对象,它是组件在内存中真实的样子;

  • ReactElement对象,React使用两个不同的类来分别处理虚拟DOM和真实DOM的载入等操作,对于虚拟DOM,这个类是ReactCompositeComponent,对真实的DOM,这个类叫ReactDOMComponent

  • 如果组件中包含类似componentWillMount这些hook的函数作为属性,则React会在相应的时间去执行它们,如果没有这些属性,则什么都不会发生。另外插一句,(目前)JavaScript里面好像还没有抽象类、抽象函数的概念?所以hook函数没法直接看继承的类的定义来获取(比如React.Component里面是没有componentWillMount的影子的),所以只能靠看官方文档了;

  • 对于虚拟DOM而言,React把它们分成了三类:StatelessFunctionalPureClassImpureClass

  • React支持直接用一个函数来表示组件(即上面的StatelessFunctional),此时函数本身就相当于普通组件的render函数,而其他所有属性都被缺省了;

  • React组件的render函数返回的虚拟DOM只能包含一个根节点,即只能是一颗树状的结构,不能是森林状的结构,即使要表现多个节点,也要用一个根节点把它们包起来,这是React官方限制的;

  • 真实的DOM是可以包含多个子节点的,它的每个子节点会被遍历逐一渲染;

  • React中组件(或元素) 的渲染是自顶向下的,即最外部的元素会被先渲染出来;

  • React组件渲染的流程粗略为以下样子:

    React render flow

  1. reactreact-dom均以“15.4.2”版本为准。