react源码阅读笔记(2)组件的渲染

本文使用的是react 15.6.1的代码

ReactDOM.render


class App extends React.Component {
    constructor(props) {
        super(props)
    }

    render() {
        let {text} = this.props;
        return 
{text}
} } App.defaultProps = { text: 'hello react' }; ReactDOM.render(( ), document.getElementById('root'));

相信大家在初学react的时候都写过类似上面的代码,上篇文章中,已经介绍了 extends React.Component做了些什么,这次,我们就来看看ReactDOM.render中react去做了些什么,ReactDOM.render实际调用的是ReactMount.js中的render
代码地址:[ReactMount]

// nextElement即ReactElement,
// container 具体容器,将由virtualDOM生成的真实dom映射的位置
// callback 渲染成功后的回调
  render: function(nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(
      null,
      nextElement,
      container,
      callback,
    );

进一步得知,其主要逻辑位于_renderSubtreeIntoContainer下
代码地址:[ReactMount]

// ReactDom.render调用后 第一个参数默认传递null,因为该方法第一个参数是父组件,剩下三个参数和render函数一致
  _renderSubtreeIntoContainer: function(
    parentComponent,
    nextElement,
    container,
    callback,
  ) {
    //判断callback是否为函数;
    ReactUpdateQueue.validateCallback(callback, 'ReactDOM.render');


    var nextWrappedElement = React.createElement(TopLevelWrapper, {
      child: nextElement,
    });
//通过TopLevelWrapper创建一个ReactElement节点,并且设置其this.props.child =     render传入的ReactElement    
     /** 上文TopLevelWrapper代码
     var topLevelRootCounter = 1;z
     var TopLevelWrapper = function() {
      this.rootID = topLevelRootCounter++;
    };
     TopLevelWrapper.prototype.isReactComponent = {};
     TopLevelWrapper.prototype.render = function() {
      return this.props.child;
    };
     TopLevelWrapper.isReactTopLevelWrapper = true;
     */
      // 可以看出TopLevelWrapper代码就是一个简单的ReactComponent,类似于 extend React.Component, 并重写了方法render
    

    var nextContext;
    // 如果存在父组件,即不是顶级组件的情况下(在ReactDOM.render时,parentComponent为null)
    if (parentComponent) {
      var parentInst = ReactInstanceMap.get(parentComponent);
      nextContext = parentInst._processChildContext(parentInst._context);
    } else {
      nextContext = emptyObject;
    }
    // 这时候preComponent = null;
    var prevComponent = getTopLevelWrapperInContainer(container);
    if (prevComponent) {
      //
      var prevWrappedElement = prevComponent._currentElement;
      var prevElement = prevWrappedElement.props.child;
      // diff 简单概括就是如果渲染的节点和原节点type和key(所以像listview可以通过设置key来进行优化)都不变的时候,直接更新就好,不用在去重新渲染一遍
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        var publicInst = prevComponent._renderedComponent.getPublicInstance();
        var updatedCallback =
          callback &&
          function() {
            callback.call(publicInst);
          };
        ReactMount._updateRootComponent(
          prevComponent,
          nextWrappedElement,
          nextContext,
          container,
          updatedCallback,
        );
        return publicInst;
      } else {
        //否则的话卸载掉该容器的组件
        ReactMount.unmountComponentAtNode(container);
      }
    }

    // 获取container的跟元素
    var reactRootElement = getReactRootElementInContainer(container);
    // 确定container是否被markup,即添加了data-reactid,第一次渲染肯定是false
    var containerHasReactMarkup =
      reactRootElement && !!internalGetID(reactRootElement);
    // 目前为false,因为ReactDOM.render调用时还没有实例化任何组件
    var containerHasNonRootReactChild = hasNonRootReactChild(container);


    // 目前为false
    var shouldReuseMarkup =
      containerHasReactMarkup &&
      !prevComponent &&
      !containerHasNonRootReactChild;
    // 关键代码,渲染,插入都在这里面
    var component = ReactMount._renderNewRootComponent(
      nextWrappedElement,
      container,
      shouldReuseMarkup,
      nextContext,
    )._renderedComponent.getPublicInstance();
    if (callback) {
      callback.call(component);
    }
    return component;
  }

根据代码我们知道,在_renderSubtreeIntoContainer方法的时候,他会优先判断渲染节点和原节点的type和key是否一致,如果一致,直接调用_updateRootComponent更新,否则才会去重新render新的组件,因此在渲染listview等需要大量刷新的组件时,可以通过设置key去优化显示,减少重新渲染

在看看_renderNewRootComponent的代码

/**
   * Render a new component into the DOM. Hooked by hooks!
   *
   * @param {ReactElement} nextElement 即将渲染的组件
   * @param {DOMElement} container 容器元素
   * @param {boolean} shouldReuseMarkup 是否需要重新标记元素
   * @return {ReactComponent} nextComponent 返回一个ReactComponent
   */
  _renderNewRootComponent: function(
    nextElement,
    container,
    shouldReuseMarkup,
    context,
  ) {
     //主要和滚动条有关,目前不需要太关心
    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    //实例化React Component,
    var componentInstance = instantiateReactComponent(nextElement, false);
    /*
     上文instantiateReactComponent
     function instantiateReactComponent(node, shouldHaveDebugID) {
     var instance;

     if (node === null || node === false) {
     // 如果是空对象
     instance = ReactEmptyComponent.create(instantiateReactComponent);
     } else if (typeof node === 'object') {
     // 如果是Node,(包括dom节点以及reactElement)
     var element = node;

     // 原生对象
     if (typeof element.type === 'string') {
     instance = ReactHostComponent.createInternalComponent(element);
     } else {
     // react组件
     instance = new ReactCompositeComponentWrapper(element);
     }
     //如果元素本来就是一个string或者number,如 
111
中的111 } else if (typeof node === 'string' || typeof node === 'number') { //创建一个 instance = ReactHostComponent.createInstanceForText(node); } //这两个参数用于dom 和 art diff算法 instance._mountIndex = 0; instance._mountImage = null; return instance; } */ /* 批量更新方法,具体实现可以见 ReactDefaultBatchingStrategy.js中 batchedUpdate方法,实际就是执行 * batchedMountComponentIntoNode方法,将后面的参数传入batchedMountComponentIntoNode中 */ ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context, ); var wrapperID = componentInstance._instance.rootID; instancesByReactRootID[wrapperID] = componentInstance; return componentInstance; },

batchedMountComponentIntoNode中,使用了React中大量使用的事务机制,调用了mountComponentIntoNode方法,事务机制后面有空在来研究,我们直接看mountComponentIntoNode


function mountComponentIntoNode(
  wrapperInstance,
  container,
  transaction,
  shouldReuseMarkup,
  context,
) {
 /*调用刚才ReactComponent instance中mountComponent方法,将React组件解析成对应的html(对应不同ReactComponent instance)mountComponent也是不同的
   
hello react
, 对应的是ReactDOMTextComponent,最终解析成的HTML为
hello react
*/ var markup = ReactReconciler.mountComponent( wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */, ); wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance; /** * 将解析出来的html插入到dom中 */ ReactMount._mountImageIntoNode( markup, container, wrapperInstance, shouldReuseMarkup, transaction, ); }

看看ReactReconciler中的mountComponent代码

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

由代码可以知道,其实本质也是调用了ReactComponent实例的mountComponent,刚才我们说到,不同的ReactComponent实例会有不同的mountComponent方法
大体有这三种实例:

  1. ReactHostComponent.createInternalComponent(element); 原生组件
  2. new ReactCompositeComponentWrapper(element); 自定义React组件
  3. ReactHostComponent.createInstanceForText(node); 字符串/数字

我们看一下最复杂的自定义React组件的mountComponent方法

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

    // Initialize the public class a
    var doConstruct = shouldConstruct(Component);
    // 本质上就是new 了一个React.createClass(), 即Component组件,代码可以自己看看,比较简单
    var inst = this._constructComponent(
      doConstruct,
      publicProps,
      publicContext,
      updateQueue,
    );
    var renderedElement;

    // 当不存在构造函数,并且没有new出来的组件实例是null或者组件实例没有render方法,那么可以认为这是一个无状态组件
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      // new 一个无状态组件
      inst = new StatelessComponent(Component);
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass;
      } else {
        this._compositeType = CompositeTypes.ImpureClass;
      }
    }

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

    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,  // 感谢钟佳锋同学的指正,这里的renderedElement是后面_renderValidatedComponent中调用内部的render方法获取
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );
    }

    if (inst.componentDidMount) { //生命周期componentDidMount
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }

    return markup;
  },

从上面我们知道自定义React组件真正实例化(new)的时候就在mountComponment之中,但是这只是简单的实例化,返回值html是在performInitialMount中处理的,在看看performInitialMount中的代码

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

    var debugID = 0;

    if (inst.componentWillMount) {
      //该实体如果有componentWillMount方法,生命周期之一
      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
    // 获取子组件,实际是调用React.createClass中的render方法,因为render方法会返回一个ReactElement对象
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

    var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    //再次调用instantiateReactComponent初始化
    var child = this._instantiateReactComponent(
      renderedElement,
      nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
    );
    this._renderedComponent = child;
    
    //再一次调用mountComponent
    var markup = ReactReconciler.mountComponent(
      child,
      transaction,
      hostParent,
      hostContainerInfo,
      this._processChildContext(context),
      debugID,
    );


    return markup;
  },

在这段代码中,renderedElement被_renderValidatedComponent重新进行了赋值,根据后面child这个变量命名,我们猜测,这个方法会返回子组件,最后,又在一次调用了ReactReconciler.mountComponent方法,和mountComponentIntoNode方法调用手段一致,只是第一个参数变成了child,简单猜测一下,这个时候在渲染子组件。为了证实猜测,在来看看_renderValidatedComponent方法

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

    return renderedElement;
  },

对应的_renderValidatedComponentWithoutOwnerOrContext方法

/**
   * @protected
   */
  _renderValidatedComponentWithoutOwnerOrContext: function() {
    var inst = this._instance;
    var renderedElement;
    //回想一下我们React.createClass中render方法里面被babel解析后是什么
    //render中{return React.createElement}
    //所以他又是一个ReactElement
    renderedElement = inst.render();

    return renderedElement;
  },

果然,是调用了render方法,也就是render中的子组件。随后组件又调用了_instantiateReactComponentfan方法初始化最后再一次mountComponent去实例化子组件,形成递归去渲染,直到得到最后的markup,同时不难想象,随着不断的递归只有下面的2类会返回正常的mask对象

  1. ReactHostComponent.createInternalComponent(element); 原生组件
  2. ReactHostComponent.createInstanceForText(node); 字符串/数字

我们在来看看createInternalComponent方法,该方法会new一个ReactDOMComponent(指代原生组件),我们来看看ReactDOMComponent的mountComponent有什么

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

    var props = this._currentElement.props;
    //针对不同的element,进行不同的处理,在构造函数中this._tag=element.tag.toLowCase()
    switch (this._tag) {
      case 'audio':
      case 'form':
      case 'iframe':
      case 'img':
      case 'link':
      case 'object':
      case 'source':
      case 'video':
        this._wrapperState = {
          listeners: null,
        };
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
      case 'input':
        ReactDOMInput.mountWrapper(this, props, hostParent);
        props = ReactDOMInput.getHostProps(this, props);
        transaction.getReactMountReady().enqueue(trackInputValue, this);
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
      case 'option':
        ReactDOMOption.mountWrapper(this, props, hostParent);
        props = ReactDOMOption.getHostProps(this, props);
        break;
      case 'select':
        ReactDOMSelect.mountWrapper(this, props, hostParent);
        props = ReactDOMSelect.getHostProps(this, props);
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
      case 'textarea':
        ReactDOMTextarea.mountWrapper(this, props, hostParent);
        props = ReactDOMTextarea.getHostProps(this, props);
        transaction.getReactMountReady().enqueue(trackInputValue, this);
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
    }

    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;

    var mountImage;
    // 调用render时,useCreateElement为true
    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}>`;
          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 + '';
      }
    }

    switch (this._tag) {
      case 'input':
        transaction.getReactMountReady().enqueue(inputPostMount, this);
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'textarea':
        transaction.getReactMountReady().enqueue(textareaPostMount, this);
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'select':
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'button':
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'option':
        transaction.getReactMountReady().enqueue(optionPostMount, this);
        break;
    }

    return mountImage;
  }

由代码可以知道react通过type对input,select,script等特殊元素进行单独的处理,最后调用createElement来生成一个普通的元素,同时调用了this._createInitialChildren(transaction, props, context, lazyTree)方法去实例化其中的子组件,同原生组件一下,形成递归返回到tree中,最后mountImage = lazyTree;拿到生成的节点,这里就不在看原生组件_createInitialChildren的实现了

回到开mountCompouentIntoNode的代码,最后调用了ReactMount._mountImageIntoNode方法,这个方法就是真真的把节点插入带容器中去了,看一下实现;

_mountImageIntoNode: function(
    markup,
    container,
    instance,
    shouldReuseMarkup,
    transaction,
  ) {

    //暂时不用关心
    if (shouldReuseMarkup) {
     ......
    }

    //render的时候该值为true
    if (transaction.useCreateElement) {
      //移除container中的节点
      while (container.lastChild) {
        container.removeChild(container.lastChild);
      }
      DOMLazyTree.insertTreeBefore(container, markup, null);
    } else {
      setInnerHTML(container, markup);
      ReactDOMComponentTree.precacheNode(instance, container.firstChild);
    }
  },

最后实际上是调用DOMLazyTree.insertTreeBefore完成节点插入,这一段代码很简单,就不在单独分析了

总结

终于走完了React整个渲染流程,我们可以发现React抽象程度非常的高,源码读起来特别的复杂,这里也只是简单的阅读整个流程,细节太多,没有深究

你可能感兴趣的:(react源码阅读笔记(2)组件的渲染)