初步看了react-dom這個包的一些源碼,發現其比react包要複雜得多,react包中基本不存在跨包調用的狀況,他所作的也僅僅是定義了ReactElement對象,封裝了對ReactElement的基本操做,而react-dom包存在複雜的函數調用。本文將對ReactDOM.render源碼作一個初步解析。
文章中若有不當之處,歡迎交流指點。react版本16.8.2
。在源碼添加的註釋在github react-source-learn。
使用react時經常寫相似下面的代碼:html
import ReactDOM from 'react-dom'; ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );
代碼1react
這裏導入的ReactDOM就是packages/react-dom/client/ReactDOM.js中所導出的對象。從文檔可見ReactDOM對象有以下幾個方法:(ps:從源碼看其實還有不少其餘方法)git
本文只介紹render()方法github
render方法定義以下:dom
render( element: React$Element<any>, container: DOMContainer, callback: ?Function, ) { invariant( // 1 isValidContainer(container), 'Target container is not a DOM element.', ); if (__DEV__) { warningWithoutStack( !container._reactHasBeenPassedToCreateRootDEV, 'You are calling ReactDOM.render() on a container that was previously ' + 'passed to ReactDOM.%s(). This is not supported. ' + 'Did you mean to call root.render(element)?', enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', ); } // 2 return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); },
代碼2async
render方法接收兩個必選參數可一個可選參數,結合代碼1的調用可知,element是一個ReactElement對象, container是一個dom節點,callback在上面的代碼1並無指定,他是一個可選函數。函數
這個render方法作的事情比較簡單,一是校驗container參數,二是調用legacyRenderSubtreeIntoContainer方法並返回。spa
接下來是legacyRenderSubtreeIntoContainercode
// 刪除了第一次調ReactDOM.render不會走的分支 function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, // ReactDOM.render 是null children: ReactNodeList, // 是一個ReactElement , ReactDOM.render是第一個參數 container: DOMContainer, // 是一個dom節點, ReactDOM.render是第二個參數 forceHydrate: boolean, // ReactDOM.render 是false callback: ?Function, // ReactDOM.render 是 第三個參數 ) { if (__DEV__) { topLevelUpdateWarnings(container); } // TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. // 根據type知道, Root type是個對象,包含 // render方法 // unmount方法 // legacy_renderSubtreeIntoContainer 方法 // createBatch 方法 // _internalRoot屬性 let root: Root = (container._reactRootContainer: any); if (!root) { // ReactDOM.render調用時走這裏 // Initial mount // 調用 legacyCreateRootFromDOMContainer 拿 Root root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, // ReactDOM.render是false ); // 在callback加參數 if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // Initial mount should not be batched. // 這個是packages/react-reconciler/ReactFiberScheduler.js中的方法 // TOLEARN: 這個裏邊應該是一些調度過程, 後續再看 unbatchedUpdates(() => { if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { // ReactDOM.render方法走這裏 // 這裏的root.render 返回的是一個叫Work的東西, TOLEARN,這個Work後面再作了解 root.render(children, callback); } }); } // getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法 // 關於他是返回的一個什麼東西, 後面再看, 總之他是我ReactDOM.render方法回調函數的一個參數, // 也是返回值 return getPublicRootInstance(root._internalRoot); }
legacyRenderSubtreeIntoContainer在第一次render時作了以下事情:htm
關於instance的獲取是調用的getPublicRootInstance,getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法,後面再研究
關於unbatchedUpdates, 這個東西是這個是packages/react-reconciler/ReactFiberScheduler.js中的方法,簡單看了看還沒太明白
接下來看一下ReactRoot實例的獲取
// 刪除了__DEV__分支的代碼 // 若須要清理container的子節點,清理, 而後new ReactRoot並返回 function legacyCreateRootFromDOMContainer( container: DOMContainer, // dom節點 forceHydrate: boolean, // false render ): Root { // 是否不須要清理container的子元素, 第一次render是false, 即須要清理 const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 第一次調用時爲false // First clear any existing content. if (!shouldHydrate) { // 第一次render走這裏 let warned = false; let rootSibling; // 這裏將container的子元素都清理掉了 while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } // Legacy roots are not async by default. const isConcurrent = false; return new ReactRoot(container, isConcurrent, shouldHydrate); }
這個方法比較簡單,在第一次調用ReactDOM.render時,shouldHydrate會是false,因此會走到if (!shouldHydrate) 分支裏,將container節點的全部子節點都清理掉,最後是new 了一個ReactRoot做爲返回值,關於ReactRoot等內容將放到後面的文章分析。
以上是ReactDOM.render的函數調用示意圖。