一、ReactDOM.render 都干啥了
我们在写react的时候,最后一步肯定是
ReactDOM.render( <div> <Home name="home"/> </div> , document.getElementById('app') );
我们上面得知jsx被解析成了虚拟dom对象,我们把一个对象和一个dom传入render方法就得到了我们的页面,好神奇呀,我们开始撸到render方法:
const ReactDOM: Object = { render( element: React$Element<any>, // react组件对象 container: DOMContainer, // 就是id为app的那个dom callback: ?Function, // callback 没有 ) { return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); } }
抛开typeScript那些恶心的类型限定不谈,我们发现render的实质就是调用并返回 legacyRenderSubtreeIntoContainer 这个函数执行后的结果,你看这个函数的命名:
legacy : 遗产 + render: 渲染 + subtree: 子树 + into: 到 + container: 容器
爱几把咋翻译咋翻译,大致意思就是说把 虚拟的dom树渲染到真实的dom容器中。此函数应当荣当 核心函数 宝座
二、legacyRenderSubtreeIntoContainer 又干了啥?
还是撸到丫的源码:
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, // null children: ReactNodeList, // element 虚拟dom树 container: DOMContainer, // html中的dom根对象 forceHydrate: boolean, // false 服务器端渲染标识 callback: ?Function, // 回调函数 没有 ) { // 对container进行校验 invariant( isValidContainer(container), 'Target container is not a DOM element.', ); // 取root对象,一般如果非服务器端渲染这个root是不存在的 let root: Root = (container._reactRootContainer: any); // 进入浏览器端渲染流程 if (!root) { // 人工制造root,附加了一堆fiber的东西到_reactRootContainer root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); if (typeof callback === 'function') { const originalCallback = callback; callback = function() { // 该变callback的this为 instance const instance = DOMRenderer.getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } DOMRenderer.unbatchedUpdates(() => { if (parentComponent != null) { // 向真实dom中挂载虚拟dom root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { // 多么直白的语义 root.render(children, callback); } }); } else { // 还是先整一下callback if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = DOMRenderer.getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // 还是上面那一套 if (parentComponent != null) { // 向root中挂载dom root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } } // 返回container 中的dom return DOMRenderer.getPublicRootInstance(root._internalRoot); }
通过看这个核心函数的代码,发现它其中有3个谜团:
1. _reactRootContainer 的制造:legacyCreateRootFromDOMContainer
这个函数会制造一个对象挂载到真实的dom根节点上,有了这个对象,执行该对象上的一些方法可以将虚拟dom变成dom树挂载到根节点上
2. DOMRenderer.unbatchedUpdates
它的回调执行了挂载dom结构的方法
3. root.legacy_renderSubtreeIntoContainer 和 root.render
如果有parentComponent 这个东西,就执行root.render 否则 root.legacy_renderSubtreeIntoContainer
三、跟进谜团
1.root的制造
找到 legacyCreateRootFromDOMContainer 函数:
1 function legacyCreateRootFromDOMContainer( 2 container: DOMContainer, 3 forceHydrate: boolean, // false 4 ): Root { 5 const shouldHydrate = 6 forceHydrate || shouldHydrateDueToLegacyHeuristic(container); 7 // 是否需要服务器端渲染 8 if (!shouldHydrate) { 9 let warned = false; 10 let rootSibling; 11 while ((rootSibling = container.lastChild)) { 12 if (__DEV__) { 13 ... 14 } 15 // 将dom根节点清空 16 container.removeChild(rootSibling); 17 } 18 } 19 if (__DEV__) { 20 ... 21 } 22 const isAsync = false; 23 return new ReactRoot(container, isAsync, shouldHydrate); 24 }