美文网首页程序员
从react源码看Virtual Dom到真实Dom的渲染过程

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

作者: 陈小俊先生 | 来源:发表于2017-08-22 17:30 被阅读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)

    相关文章

      网友评论

        本文标题:从react源码看Virtual Dom到真实Dom的渲染过程

        本文链接:https://www.haomeiwen.com/subject/zyildxtx.html