從react源碼看Virtual Dom到真實Dom的渲染過程

很多人都看過許多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)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容