React 源码深度解读(三):首次 DOM 元素渲染 - Part 3
前言
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习 React 源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。本文会大量用到原文中的例子,想体会原汁原味的感觉,推荐阅读原文。
本系列文章基于 React 15.4.2 ,以下是本系列其它文章的传送门:
React 源码深度解读(一):首次 DOM 元素渲染 - Part 1
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
React 源码深度解读(三):首次 DOM 元素渲染 - Part 3
React 源码深度解读(四):首次自定义组件渲染 - Part 1
React 源码深度解读(五):首次自定义组件渲染 - Part 2
React 源码深度解读(六):依赖注入
React 源码深度解读(七):事务 - Part 1
React 源码深度解读(八):事务 - Part 2
React 源码深度解读(九):单个元素更新
React 源码深度解读(十):Diff 算法详解
正文
上一篇讲解了平台无关的代码,这篇继续来讲针对与 HTML DOM 操作的代码。
|=ReactMount.render(nextElement, container, callback) ___ |=ReactMount._renderSubtreeIntoContainer() | |-ReactMount._renderNewRootComponent() | |-instantiateReactComponent() | |~batchedMountComponentIntoNode() upper half |~mountComponentIntoNode() (平台无关) |-ReactReconciler.mountComponent() | |-ReactCompositeComponent.mountComponent() | |-ReactCompositeComponent.performInitialMount() | |-instantiateReactComponent() _|_ |-ReactDOMComponent.mountComponent() lower half |-_mountImageIntoNode() (HTML DOM 相关) _|_
先来看看 ReactDOMComponent.mountComponent 做了什么:
// 文件位置:src/renderers/dom/shared/ReactDomComponent.js tComponent: function ( transaction, hostParent, hostContainerInfo, context ) { ... var mountImage; var ownerDocument = hostContainerInfo._ownerDocument; var el; // document.createElement 创建 h1 元素 el = ownerDocument.createElement(this._currentElement .type, props.is); ... // 创建 component 与 DOM 的双向链接 // ReactDOMComponent[ins]._hostNode 指向 DOM // DOM 的 __reactInternalInstance 指向 component ReactDOMComponentTree.precacheNode(this, el); // 设置属性 this._updateDOMProperties(null, props, transaction); var lazyTree = DOMLazyTree(el); // 循环创建子元素 this._createInitialChildren(transaction, props, context, lazyTree); mountImage = lazyTree; ... return mountImage; },
到此为止,实例间的关系是这样的:
DOM 元素创建完成后,剩下的就是将其挂载到 container 上面去了。这里调用的是 ReactMount 的 _mountImageIntoNode:
|=ReactMount.render(nextElement, container, callback) ___ |=ReactMount._renderSubtreeIntoContainer() | |-ReactMount._renderNewRootComponent() | |-instantiateReactComponent() | |~batchedMountComponentIntoNode() upper half |~mountComponentIntoNode( (平台无关) wrapperInstance, // scr: -> not of interest now | container, // scr: --> document.getElementById(‘root’) transaction, // scr: --> not of interest | shouldReuseMarkup, // scr: -------> null | context, // scr: -------> not of interest ) | |-ReactReconciler.mountComponent() | |-ReactCompositeComponent.mountComponent() | |-ReactCompositeComponent.performInitialMount() | |-instantiateReactComponent() _|_ |-ReactDOMComponent.mountComponent() | /* we are here */ lower half |-_mountImageIntoNode() (HTML DOM 相关) markup, // scr: --> DOMLazyTree[ins] | container, // scr: --> document.getElementById(‘root’) wrapperInstance, // scr:----> same | shouldReuseMarkup, // scr:--> same | transaction, // scr: -------> same | )
具体实现:
_mountImageIntoNode: function ( markup, // DOMLazyTree[ins] container, // document.getElementById(‘root’) instance, shouldReuseMarkup, transaction ) { ... while (container.lastChild) { container.removeChild(container.lastChild); } DOMLazyTree.insertTreeBefore(container, markup, null); ... }
DOMLazyTree.insertTreeBefore 最终会调用 parentNode.insertBefore,将元素挂载到 container 上。到此为止,首次渲染就完成啦!
总结
从 React 启动到元素渲染到页面,并不像看起来这么简单,中间经历了复杂的层级调用。原文的这张图总结得非常好: