摘要:一更新的方式有三種渲染接下來,我們就來看下源碼二作用在提供的里渲染一個元素,并返回對該組件的引用常見的用法是這個官網(wǎng)網(wǎng)址源碼服務(wù)端使用方法渲染節(jié)點是讓服務(wù)端盡可能復(fù)用節(jié)點,提高性能元素容器應(yīng)用渲染結(jié)束后,調(diào)用的函數(shù)錯誤抓取方法本質(zhì)是返回
一、React更新的方式有三種:
(1)ReactDOM.render() || hydrate(ReactDOMServer渲染)
(2)setState
(3)forceUpdate
接下來,我們就來看下ReactDOM.render()源碼
二、ReactDOM.render(element, container[, callback])
作用:
在提供的container里渲染一個React元素,并返回對該組件的引用
常見的用法是這個:
ReactDOM.render(, document.getElementById("root"));
官網(wǎng)網(wǎng)址:
https://zh-hans.reactjs.org/docs/react-dom.html#render
源碼:
const ReactDOM: Object = { //服務(wù)端使用hydrate方法渲染節(jié)點 hydrate(element: React$Node, container: DOMContainer, callback: ?Function) { invariant( isValidContainer(container), "Target container is not a DOM element.", ); // TODO: throw or warn if we couldn"t hydrate? return legacyRenderSubtreeIntoContainer( null, element, container, //true是讓服務(wù)端盡可能復(fù)用節(jié)點,提高性能 true, callback, ); }, render( //元素 element: React$Element, //容器 container: DOMContainer, //應(yīng)用渲染結(jié)束后,調(diào)用的函數(shù) callback: ?Function, ) { //錯誤抓取 invariant( isValidContainer(container), "Target container is not a DOM element.", ); //render方法本質(zhì)是返回了函數(shù)legacyRenderSubtreeIntoContainer return legacyRenderSubtreeIntoContainer( null, element, container, //render不會復(fù)用節(jié)點,因為是前端渲染 false, callback, ); }, }
解析:
(1)render()方法本質(zhì)是返回了函數(shù)legacyRenderSubtreeIntoContainer()
(2)hydrate()和render()的唯一區(qū)別是傳入legacyRenderSubtreeIntoContainer()的第四個參數(shù)不一樣:
hydrate()為true,表示在服務(wù)端盡可能復(fù)用節(jié)點,提高性能;
render()為false,表示在瀏覽器端不會去復(fù)用節(jié)點,而是全部替換掉。
三、legacyRenderSubtreeIntoContainer()
作用:
初始化Container
源碼:
// null, element, container, false, callback, function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component, children: ReactNodeList, container: DOMContainer, forceHydrate: boolean, callback: ?Function, ) { // TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. //render中一般渲染的是DOM標簽,所以不會有_reactRootContainer存在, // 所以第一次渲染,root是不存在的 let root: _ReactSyncRoot = (container._reactRootContainer: any); let fiberRoot; if (!root) { // Initial mount //創(chuàng)建一個ReactRooter root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot; //判斷是否有callback if (typeof callback === "function") { const originalCallback = callback; callback = function() { //根據(jù)fiberRoot獲取公共Root實例 //就是fiberRoot.current.child.stateNode const instance = getPublicRootInstance(fiberRoot); //通過該實例instance 去調(diào)用originalCallback方法 originalCallback.call(instance); }; } // Initial mount should not be batched. //初始化安裝不應(yīng)該批量更新 unbatchedUpdates(() => { //element,fiberRoot,null,callback updateContainer(children, fiberRoot, parentComponent, callback); }); } else { fiberRoot = root._internalRoot; if (typeof callback === "function") { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // Update updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }
解析:
(1)由于是第一次渲染更新,所以root是null,只需看!root的情況
(2)legacyCreateRootFromDOMContainer(container,false,)的作用是創(chuàng)建ReactRooter,稍后會講解
(3)unbatchedUpdates(fn)的簡化源碼如下:
unbatchedUpdates(fn){ return fn() }
(4)updateContainer()的作用是更新container,稍后講解
四、legacyCreateRootFromDOMContainer(container,forceHydrate,)
作用:
創(chuàng)建一個ReactRooter
源碼:
//創(chuàng)建ReactRooter function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): _ReactSyncRoot { //是否是服務(wù)端渲染 const shouldHydrate = //render的forceHydrate是false,所以會調(diào)用shouldHydrateDueToLegacyHeuristic方法來判斷是否是服務(wù)端渲染 forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. //如果不是服務(wù)端渲染的話 if (!shouldHydrate) { let warned = false; let rootSibling; //循環(huán)刪除container的子節(jié)點 //為什么要刪除?因為React認為這些節(jié)點是不需要復(fù)用的 while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } // Legacy roots are not batched. //container是空的container,0,false //ReactRoot是同步的 //sync 同步 //async 異步 return new ReactSyncRoot(container, LegacyRoot, shouldHydrate); }
解析:
(1)render()的forceHydrate是false,所以看shouldHydrateDueToLegacyHeuristic(container)是否返回false
① shouldHydrateDueToLegacyHeuristic()
作用:
判斷是否是服務(wù)端渲染
源碼:
//判斷是否是服務(wù)端渲染 function shouldHydrateDueToLegacyHeuristic(container) { //獲取container的第一個節(jié)點(根節(jié)點) //也就是 id="root" 的節(jié)點 const rootElement = getReactRootElementInContainer(container); return !!( rootElement && rootElement.nodeType === ELEMENT_NODE && //判斷是否是服務(wù)端渲染 rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME) ); }
② getReactRootElementInContainer()
作用:
獲取container中的第一個節(jié)點(或文檔節(jié)點)
源碼:
//獲取Container里的RectRoot元素 //返回document節(jié)點或第一個子節(jié)點 function getReactRootElementInContainer(container: any) { if (!container) { return null; } //DOCUMENT_NODE 即 window.document if (container.nodeType === DOCUMENT_NODE) { return container.documentElement; } else { return container.firstChild; } }
也就是說,判斷是否是服務(wù)端渲染的標志是:
在獲取container中的第一個節(jié)點(或文檔節(jié)點)后,看該節(jié)點是否有屬性ROOT_ATTRIBUTE_NAME
ROOT_ATTRIBUTE_NAME是什么呢?
//服務(wù)端渲染的話,會在React App的第一個元素上添加該屬性 //以標識是服務(wù)端渲染的 export const ROOT_ATTRIBUTE_NAME = "data-reactroot";
即data-reactroot
(2)由(1)可知,render()的container的首節(jié)點是沒有data-reactroot屬性的,所以會進行while循環(huán),依次刪除container的子節(jié)點,刪除完畢后,new 一個ReactSyncRoot()的實例
(3)ReactSyncRoot()
作用:
創(chuàng)建ReactRoot實例
源碼:
// container,0,false function ReactSyncRoot( container: DOMContainer, tag: RootTag, hydrate: boolean, ) { // Tag is either LegacyRoot or Concurrent Root const root = createContainer(container, tag, hydrate); this._internalRoot = root; }
把創(chuàng)建的root作為legacyCreateRootFromDOMContainer()的__internalRoot屬性
① createContainer
作用:
創(chuàng)建React容器
源碼:
//創(chuàng)建React容器 export function createContainer( containerInfo: Container, tag: RootTag, hydrate: boolean, ): OpaqueRoot { //創(chuàng)建FiberRoot return createFiberRoot(containerInfo, tag, hydrate); }
也就是說legacyCreateRootFromDOMContainer()的本質(zhì)是創(chuàng)建了FilberRoot
五、updateContainer()
作用:
創(chuàng)建更新container
源碼:
//更新Container export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component, callback: ?Function, ): ExpirationTime { const current = container.current; //1073741823 const currentTime = requestCurrentTime(); const suspenseConfig = requestCurrentSuspenseConfig(); //計算過期時間,這是React優(yōu)先級更新非常重要的點 const expirationTime = computeExpirationForFiber( currentTime, current, suspenseConfig, ); return updateContainerAtExpirationTime( element, container, parentComponent, expirationTime, suspenseConfig, callback, ); }
解析:
(1)requestCurrentTime()
作用:
計算新開始的時間
源碼不用看,只需要知道該時間,是以V8引擎上最大31位整數(shù)1073741823為根據(jù)的:
// Max 31 bit integer. The max integer size in V8 for 32-bit systems. // Math.pow(2, 30) - 1 // 0b111111111111111111111111111111 //整型最大數(shù)值,是V8中針對32位系統(tǒng)所設(shè)置的最大值 export default 1073741823;
(2)requestCurrentSuspenseConfig()和computeExpirationForFiber()以后會講解
(3)updateContainerAtExpirationTime()
作用:
每到過期時間,就更新container,過期時間單位為 10ms
源碼:
//在過期時間內(nèi),更新container export function updateContainerAtExpirationTime( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) { // TODO: If this is a nested container, this won"t be the root. const current = container.current; //由于parentComponent為null,所以返回空對象{} const context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } //計劃更新Root return scheduleRootUpdate( current, element, expirationTime, suspenseConfig, callback, ); }
解析:
① scheduleRootUpdate()
作用:
計劃更新Root
源碼:
//計劃更新Root function scheduleRootUpdate( current: Fiber, element: ReactNodeList, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) { //創(chuàng)建更新的時間節(jié)點 const update = createUpdate(expirationTime, suspenseConfig); // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { warningWithoutStack( typeof callback === "function", "render(...): Expected the last optional `callback` argument to be a " + "function. Instead received: %s.", callback, ); update.callback = callback; } if (revertPassiveEffectsChange) { flushPassiveEffects(); } //一整個React應(yīng)用中,會有多次更新,而這多次更新均在更新隊列中 enqueueUpdate(current, update); //進行任務(wù)調(diào)度 //當React進行Update后,就要進行調(diào)度 //即 根據(jù)任務(wù)的優(yōu)先級去調(diào)度任務(wù) //先執(zhí)行優(yōu)先級高的任務(wù), scheduleWork(current, expirationTime); return expirationTime; }
解析:
任務(wù)調(diào)度是React中最重要、復(fù)雜的內(nèi)容,之后會慢慢來解析。
這里可以看到,React將初始化的Update放入了更新隊列中,并進行任務(wù)調(diào)度,最終返回了一個expirationTime
也就是說,updateContainer()本質(zhì)是返回了expirationTime
六、getPublicRootInstance()
作用:
獲取root實例
源碼:
//獲取root實例 export function getPublicRootInstance( container: OpaqueRoot, ): React$Component| PublicInstance | null { //看到container.current,我就想到了ref(xxx.current) //獲取當前節(jié)點 const containerFiber = container.current; if (!containerFiber.child) { return null; } switch (containerFiber.child.tag) { case HostComponent: return getPublicInstance(containerFiber.child.stateNode); default: return containerFiber.child.stateNode; } }
解析:
由于是 React 初始化,所以container.current是沒有子節(jié)點的,所以該方法返回 null
七、ReactDOM.render()流程圖
總結(jié):
ReactDOM.render() 的更新步驟
(1)創(chuàng)建 ReactRoot,ReactRoot 是創(chuàng)建整個React應(yīng)用的根對象
(2)創(chuàng)建 FiberRoot 和 RootFiber
(3)創(chuàng)建更新 (創(chuàng)建更新后,就會進入調(diào)度階段,調(diào)度階段由調(diào)度器進行管理)
GitHub:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-dom/src/client/ReactDOM.js
(完)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/106728.html
摘要:本文將對源碼做一個初步解析。首先在方法中校驗參數(shù)是否合法,然后調(diào)用在中,調(diào)用拿到了的一個實例,調(diào)用拿到了,用于注入到,和作為返回值,調(diào)用開始調(diào)度過程在中,首先清理了中的所有子節(jié)點,然后了一個并返回是如何調(diào)度的是一個什么樣的類的操作是在哪里 初步看了react-dom這個包的一些源碼,發(fā)現(xiàn)其比react包要復(fù)雜得多,react包中基本不存在跨包調(diào)用的情況,他所做的也僅僅是定義了React...
摘要:查看創(chuàng)建核心函數(shù)源碼行調(diào)用函數(shù)創(chuàng)建是相關(guān),不用管源碼行這個指的是調(diào)用創(chuàng)建,下面我們將會說到對象源碼行源碼行函數(shù)中,首先創(chuàng)建了一個,然后又創(chuàng)建了一個,它們兩者還是相互引用。 感謝 yck: 剖析 React 源碼解析,本篇文章是在讀完他的文章的基礎(chǔ)上,將他的文章進行拆解和加工,加入我自己的一下理解和例子,便于大家理解。覺得yck寫的真的很棒 。React 版本為 16.8.6,關(guān)于源碼的...
摘要:的創(chuàng)建組件,其實根源還是調(diào)用了編譯之后一般寫法建議用來進行源碼的跟蹤鏈接從源碼角度來看創(chuàng)建一個組件的過程中發(fā)生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...
摘要:比如有個組件,它用來實時的獲取鼠標的位置。命名空間,多個修改同一個導(dǎo)致的命名沖突。據(jù)說源碼里面為每個組件增加路由屬性就是通過該方法好了大功完成了,歡迎一起討論學(xué)習(xí)個人博客地址意卿 1.mixins 寫過react項目的應(yīng)該都碰到過,不同組件復(fù)用相同代碼的問題,在react早期使用React.createClass創(chuàng)建組件的時代,我們經(jīng)常使用的是mixins來實現(xiàn)代碼復(fù)用。比如有個組件A...
摘要:本文是造輪系列第二篇。實現(xiàn)方式事件處理跟差不多,唯一多了一步就是當點擊或者的時候,如果外部有回調(diào)就需要調(diào)用對應(yīng)的回調(diào)函數(shù)。 本文是React造輪系列第二篇。 1.React 造輪子系列:Icon 組件思路 本輪子是通過 React + TypeScript + Webpack 搭建的,至于環(huán)境的搭建這邊就不在細說了,自己動手谷歌吧。當然可以參考我的源碼。 想閱讀更多優(yōu)質(zhì)文章請猛戳Git...
閱讀 1070·2023-04-26 02:49
閱讀 1242·2021-11-25 09:43
閱讀 2626·2021-11-18 10:02
閱讀 2987·2021-10-18 13:32
閱讀 1336·2019-08-30 13:54
閱讀 2146·2019-08-30 12:58
閱讀 3078·2019-08-29 14:06
閱讀 2220·2019-08-28 18:10