程序员

从react源码看Virtual Dom到真实Dom的渲染过程

2017-08-22  本文已影响0人  陈小俊先生

很多人都看过许多React的Virtual Dom的文章,背熟了好多生命周期函数,然而,对于一个Virtual Dom渲染成一个真实Dom的过程你是否真的研究过呢?

ReactDOM.render(
    <h1>Hello World</h1>, 
    document.getElementById('root')
);

以上代码用babel转义过来就是:

ReactDOM.render(React.createElement(
    'h1',
    null,
    'Hello World'
), document.getElementById('root'));

ReactElement就是我们常说的Virtual Dom,下面我们将讨论react将一个ReactElement渲染成真实Dom的过程。

我们先看一下ReactElement.js源码:链接

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;
ReactElement.createElement = function(type, config, children) {
  var propName;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
};

以下是我们创建的Hello World的ReactElement


{
    $$typeof: Symbol(react.element)
    key: null
    props: {children: "Hello World"}
    ref: null
    type: "h1"
    _owner: null
    _store: {validated: false}
    _self: null
    _source: null
    __proto__: Object
}

接下来我们看render函数源码:链接

在render里面,调用_renderSubtreeIntoContainer,_renderSubtreeIntoContainer里又调用_renderNewRootComponent,_renderNewRootComponent生成一个ReactCompositeComponentWrapper并返回。

一个很重要的数据结构:ReactCompositeComponentWrapper

instantiateReactComponent方法本质上是个工厂函数,它在内部会对ReactElement类型进行判断,返回一个ReactCompositeComponentWrapper。

我么来看instantiateReactComponent源码:

function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;
  if (node === null || node === false) {
    // 1 ReactDOMEmptyComponent:空对象
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    if (typeof element.type === 'string') {
        // 2 ReactDOMComponent:DOM原生对象
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      instance = new element.type(element);
      if (!instance.getHostNode) {
        instance.getHostNode = instance.getNativeNode;
      }
    } else {
        // 3 ReactCompositeComponent:React自定义对象
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    // 4 ReactDOMTextComponent:文本对象
    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;
    }
  return instance;
}

一个ReactCompositeComponentWrapper长这样:

{
    _calledComponentWillUnmount: false
    _compositeType: null
    _context: null
    // 包了一层的ReactElement
    _currentElement: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
    _debugID: 0
    _hostContainerInfo: null
    _hostParent: null
    _instance: null
    _mountImage: null
    _mountIndex: 0
    _mountOrder: 0
    _pendingCallbacks: null
    _pendingElement: null
    _pendingForceUpdate: false
    _pendingReplaceState: false
    _pendingStateQueue: null
    _renderedComponent: null
    _renderedNodeType:  null
    _rootNodeID: 0
    _topLevelWrapper: null
    _updateBatchNumber: null
    _warnedAboutRefsInRender: false
    __proto
}

接下来是我认为最复杂的一部分,也就是负责将ReactCompositeComponentWrapper渲染成真实Dom的部分

_renderNewRootComponent源码:

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
    // Various parts of our code (such as ReactCompositeComponent's
    // _renderValidatedComponent) assume that calls to render aren't nested;
    // verify that that's the case.
    process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, '_renderNewRootComponent(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from ' + 'render is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : void 0;

    !isValidContainer(container) ? process.env.NODE_ENV !== 'production' ? invariant(false, '_registerComponent(...): Target container is not a DOM element.') : _prodInvariant('37') : void 0;

    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    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);

    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;

    return componentInstance;
  }

componentInstance是由instantiateReactComponent生成的一个ReactCompositeComponentWrapper。

在ReactUpdates.batchedUpdates里面,通过mountComponent方法将ReactCompositeComponentWrapper转化为一个markup
一个markup长这样:

{
    children:[]
    html:null
    node:h1
    text:null
    toString:ƒ toString()
    __proto__:Object
}

markup通过mountComponentIntoNode、_mountImageIntoNode最终渲染为真实Dom!!!

知道了这么个过程,也许对后面研究生命周期,diff函数有很大的帮助吧。

相关链接:React源码系列之初次渲染

React源码分析2 — 组件和对象的创建(createClass,createElement)

上一篇下一篇

猜你喜欢

热点阅读