一、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 }
我們發現實際上該函數做的只是在非ssr的情況下,將dom根節點清空,然后返回一個new ReactRoot(...)
那么重點又跑到了ReactRoot中:

1 // 構造函數 2 function ReactRoot( 3 container: Container, // 被清空的dom根節點 4 isAsync: boolean, // false 5 hydrate: boolean // false 6 ) { 7 // 追查之后發現:createFiberRoot(containerInfo, isAsync, hydrate); 8 // root 實際上就和fiber有了聯系 9 const root = DOMRenderer.createContainer(container, isAsync, hydrate); 10 this._internalRoot = root; 11 } 12 13 14 // 原型方法 15 16 // 渲染 17 ReactRoot.prototype.render = function( 18 children: ReactNodeList, 19 callback: ?() => mixed, 20 ): Work { 21 const root = this._internalRoot; 22 const work = new ReactWork(); 23 callback = callback === undefined ? null : callback; 24 25 if (callback !== null) { 26 work.then(callback); 27 } 28 DOMRenderer.updateContainer(children, root, null, work._onCommit); 29 return work; 30 }; 31 32 // 銷毀組件 33 ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work { 34 ... 35 }; 36 37 38 ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function( 39 parentComponent: ?React$Component<any, any>, 40 children: ReactNodeList, 41 callback: ?() => mixed, 42 ): Work { 43 const root = this._internalRoot; 44 const work = new ReactWork(); 45 callback = callback === undefined ? null : callback; 46 if (callback !== null) { 47 work.then(callback); 48 } 49 DOMRenderer.updateContainer(children, root, parentComponent, work._onCommit); 50 return work; 51 }; 52 53 ReactRoot.prototype.createBatch = function(): Batch { 54 ..... 55 };
通過以上代碼我們就了解到root到底是個啥玩意兒,這個root有render等方法外,同時還附加了一個和fiber相關的 _internalRoot屬性。
由此可知,不管是root.render 還是 root.legacy_renderSubtreeIntoContainer 都會去執行 DOMRenderer.updateContainer方法,無非就是傳入的參數時:第三個參數傳什么 而已。
2.DOMRenderer.unbatchedUpdates干了什么

1 // 正在批量更新標識 2 let isBatchingUpdates: boolean = false; 3 // 未批量更新標識 4 let isUnbatchingUpdates: boolean = false; 5 // 非批量更新操作 6 function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R { 7 // 如果正在批量更新 8 if (isBatchingUpdates && !isUnbatchingUpdates) { 9 // 未批量更新設為true 10 isUnbatchingUpdates = true; 11 try { 12 // 運行入參函數且返回執行結果 13 return fn(a); 14 } finally { 15 // 仍舊將未批量更新設為false 16 isUnbatchingUpdates = false; 17 } 18 } 19 // 不管是否在批量更新流程中,都執行入參函數 20 return fn(a); 21 }
記住這里兩個十分重要的標識:isBatchingUpdates 和 isUnbatchingUpdates 初始值都是false
由此可知 unbatchedUpdates 無論如何都會執行入參函數,無非在批量更新的時候多一些流程控制。這里留坑
3. root.legacy_renderSubtreeIntoContainer 和 root.render 在弄什么?
通過上面的層層扒皮,無論怎樣判斷,最終都會到以上兩個方法中,而這兩個方法的核心就是 DOMRenderer.updateContainer,無非就是傳不傳父組件而已
傳入的參數有: 1:虛擬dom對象樹 2:之前造出來的root中和fiber相關的_internalRoot 3.父組件(null 或 父組件) 4.回調函數

1 export function updateContainer( 2 element: ReactNodeList, // 虛擬dom對象 3 container: OpaqueRoot, // 被制造出來的fiber root 4 parentComponent: ?React$Component<any, any>, // null 5 callback: ?Function, //沒傳 6 ): ExpirationTime { 7 // 還記得虛擬dom對象 8 const current = container.current; 9 const currentTime = requestCurrentTime(); 10 const expirationTime = computeExpirationForFiber(currentTime, current); // 計算優先級 11 return updateContainerAtExpirationTime( 12 element, 13 container, 14 parentComponent, 15 expirationTime, 16 callback, 17 ); 18 } 19 // 剝皮 20 21 // 根據渲染優先級更新dom 22 export function updateContainerAtExpirationTime( 23 element: ReactNodeList, // 虛擬dom對象 24 container: OpaqueRoot, // 和fiber相關的_internalRoot 25 parentComponent: ?React$Component<any, any>, // 可有可無 26 expirationTime: ExpirationTime, // 計算出來的渲染優先級 27 callback: ?Function, // 回調函數,沒有 28 ) { 29 const current = container.current; 30 31 if (__DEV__) { 32 ... 33 } 34 // 獲取到父組件內容 35 const context = getContextForSubtree(parentComponent); 36 // 賦值操作,不知道干啥用 37 if (container.context === null) { 38 container.context = context; 39 } else { 40 container.pendingContext = context; 41 } 42 // 又到了下一站:schedule:安排, Root: 根, Update:更新 43 return scheduleRootUpdate(current, element, expirationTime, callback); 44 } 45 // 剝皮 46 47 // 安排根節點更新 48 function scheduleRootUpdate( 49 current: Fiber, // fiber對象 50 element: ReactNodeList, // 虛擬dom樹 51 expirationTime: ExpirationTime, // 更新優先級 52 callback: ?Function, // 回調 53 ) { 54 if (__DEV__) { 55 ... 56 } 57 /* 58 export const UpdateState = 0; 59 export function createUpdate(expirationTime: ExpirationTime): Update<*> { 60 return { 61 expirationTime: expirationTime, 62 63 tag: UpdateState, 64 payload: null, 65 callback: null, 66 67 next: null, 68 nextEffect: null, 69 }; 70 } 71 */ 72 73 // 返回一個包含以上屬性的update對象 74 const update = createUpdate(expirationTime); 75 // 將虛擬dom樹放入payload 76 update.payload = {element}; 77 78 callback = callback === undefined ? null : callback; 79 if (callback !== null) { 80 warningWithoutStack( 81 typeof callback === 'function', 82 'render(...): Expected the last optional `callback` argument to be a ' + 83 'function. Instead received: %s.', 84 callback, 85 ); 86 update.callback = callback; 87 } 88 // 開始加入更新隊列了,又得剝皮 89 enqueueUpdate(current, update); 90 // 91 scheduleWork(current, expirationTime); 92 return expirationTime; 93 } 94 95 96 // 更新隊列 97 // 核心update 98 export function enqueueUpdate<State>( 99 fiber: Fiber, 100 update: Update<State> // 上文那個update對象 101 ) { 102 // 根據fiber的指示進行更新 103 ... 104 }
根據一系列的剝皮,最終指向了enqueueUpdate 這個函數,而這個函數和fiber是緊密耦合的,fiber是一個棘手的問題,不理解fiber就無法弄清虛擬dom如何更新到真實dom中。
預知fiber如何,且聽后續分曉!!!
四、本次的坑有以下幾個:
1. _internalRoot 如何制造出來的,丫有什么作用,為什么后面的函數參數都傳它
2. enqueueUpdate 跟fiber的關系還不清不楚
3. expirationTime 是干什么的,它的這個優先級有什么用呢?
下期解答!