網(wǎng)站首頁 編程語言 正文
引言
React 渲染過程,即ReactDOM.render
執(zhí)行過程分為兩個大的階段:render
階段以及 commit
階段。React.hydrate
渲染過程和ReactDOM.render
差不多,兩者之間最大的區(qū)別就是,ReactDOM.hydrate
在 render
階段,會嘗試復(fù)用(hydrate)瀏覽器現(xiàn)有的 dom 節(jié)點(diǎn),并相互關(guān)聯(lián) dom 實(shí)例和 fiber,以及找出 dom 屬性和 fiber 屬性之間的差異。
Demo
這里,我們在 index.html
中直接返回一段 html,以模擬服務(wù)端渲染生成的 html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Mini React</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <div id="root"><div id="root"><div id="container"><h1 id="A">1<div id="A2">A2</div></h1><p id="B"><span id="B1">B1</span></p><span id="C">C</span></div></div></div> </body> </html>
注意,root
里面的內(nèi)容不能換行,不然客戶端hydrate
的時候會提示服務(wù)端和客戶端的模版不一致。
新建 index.jsx:
import React from "react"; import ReactDOM from "react-dom"; class Home extends React.Component { constructor(props) { super(props); this.state = { count: 1, }; } render() { const { count } = this.state; return ( <div id="container"> <div id="A"> {count} <div id="A2">A2</div> </div> <p id="B"> <span id="B1">B1</span> </p> </div> ); } } ReactDOM.hydrate(<Home />, document.getElementById("root"));
對比服務(wù)端和客戶端的內(nèi)容可知,服務(wù)端h1#A
和客戶端的div#A
不同,同時服務(wù)端比客戶端多了一個span#C
在客戶端開始執(zhí)行之前,即 ReactDOM.hydrate
開始執(zhí)行前,由于服務(wù)端已經(jīng)返回了 html 內(nèi)容,瀏覽器會立馬顯示內(nèi)容。對應(yīng)的真實(shí) DOM 樹如下:
注意,這不是 fiber 樹?。?/p>
ReactDOM.render
先來回顧一下 React 渲染更新過程,分為兩大階段,五小階段:
render 階段
- beginWork
- completeUnitOfWork
commit 階段。
- commitBeforeMutationEffects
- commitMutationEffects
- commitLayoutEffects
React 在 render 階段會根據(jù)新的 element tree 構(gòu)建 workInProgress 樹,收集具有副作用的 fiber 節(jié)點(diǎn),構(gòu)建副作用鏈表。
特別是,當(dāng)我們調(diào)用ReactDOM.render
函數(shù)在客戶端進(jìn)行第一次渲染時,render
階段的completeUnitOfWork
函數(shù)針對HostComponent
以及HostText
類型的 fiber 執(zhí)行以下 dom 相關(guān)的操作:
- 調(diào)用
document.createElement
為HostComponent
類型的 fiber 節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 實(shí)例?;蛘哒{(diào)用document.createTextNode
為HostText
類型的 fiber 節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 實(shí)例 - 將 fiber 節(jié)點(diǎn)關(guān)聯(lián)到真實(shí) dom 的
__reactFiber$rsdw3t27flk
(后面是隨機(jī)數(shù))屬性上。 - 將 fiber 節(jié)點(diǎn)的
pendingProps
屬性關(guān)聯(lián)到真實(shí) dom 的__reactProps$rsdw3t27flk
(后面是隨機(jī)數(shù))屬性上 - 將真實(shí)的 dom 實(shí)例關(guān)聯(lián)到
fiber.stateNode
屬性上:fiber.stateNode = dom
。 - 遍歷
pendingProps
,給真實(shí)的dom
設(shè)置屬性,比如設(shè)置 id、textContent 等
React 渲染更新完成后,React 會為每個真實(shí)的 dom 實(shí)例掛載兩個私有的屬性:__reactFiber$
和__reactProps$
,以div#container
為例:
ReactDOM.hydrate
hydrate
中文意思是水合物,這樣理解有點(diǎn)抽象。根據(jù)源碼,我更樂意將hydrate
的過程描述為:React 在 render 階段,構(gòu)造 workInProgress 樹時,同時按相同的順序遍歷真實(shí)的 DOM 樹,判斷當(dāng)前的 workInProgress fiber 節(jié)點(diǎn)和同一位置的 dom 實(shí)例是否滿足hydrate
的條件,如果滿足,則直接復(fù)用當(dāng)前位置的 DOM 實(shí)例,并相互關(guān)聯(lián) workInProgress fiber 節(jié)點(diǎn)和真實(shí)的 dom 實(shí)例,比如:
fiber.stateNode = dom; dom.__reactProps$ = fiber.pendingProps; dom.__reactFiber$ = fiber;
如果 fiber 和 dom 滿足hydrate
的條件,則還需要找出dom.attributes
和fiber.pendingProps
之間的屬性差異。
遍歷真實(shí) DOM 樹的順序和構(gòu)建 workInProgress 樹的順序是一致的。都是深度優(yōu)先遍歷,先遍歷當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),子節(jié)點(diǎn)都遍歷完了以后,再遍歷當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)。因?yàn)橹挥邪聪嗤捻樞?,fiber 樹同一位置的 fiber 節(jié)點(diǎn)和 dom 樹同一位置的 dom 節(jié)點(diǎn)才能保持一致
只有類型為HostComponent
或者HostText
類型的 fiber 節(jié)點(diǎn)才能hydrate
。這一點(diǎn)也很好理解,React 在 commit 階段,也就只有這兩個類型的 fiber 節(jié)點(diǎn)才需要執(zhí)行 dom 操作。
fiber 節(jié)點(diǎn)和 dom 實(shí)例是否滿足hydrate
的條件:
- 對于類型為
HostComponent
的 fiber 節(jié)點(diǎn),如果當(dāng)前位置對應(yīng)的 DOM 實(shí)例nodeType
為ELEMENT_NODE
,并且fiber.type === dom.nodeName
,那么當(dāng)前的 fiber 可以混合(hydrate) - 對于類型為
HostText
的 fiber 節(jié)點(diǎn),如果當(dāng)前位置對應(yīng)的 DOM 實(shí)例nodeType
為TEXT_NODE
,同時fiber.pendingProps
不為空,那么當(dāng)前的 fiber 可以混合(hydrate)
hydrate
的終極目標(biāo)就是,在構(gòu)造 workInProgress 樹的過程中,盡可能的復(fù)用當(dāng)前瀏覽器已經(jīng)存在的 DOM 實(shí)例以及 DOM 上的屬性,這樣就無需再為 fiber 節(jié)點(diǎn)創(chuàng)建 DOM 實(shí)例,同時對比現(xiàn)有的 DOM 的attribute
以及 fiber 的pendingProps
,找出差異的屬性。然后將 dom 實(shí)例和 fiber 節(jié)點(diǎn)相互關(guān)聯(lián)(通過 dom 實(shí)例的__reactFiber$
以及__reactProps$
,fiber 的 stateNode 相互關(guān)聯(lián))
hydrate 過程
React 在 render 階段構(gòu)造HostComponent
或者HostText
類型的 fiber 節(jié)點(diǎn)時,會首先調(diào)用 tryToClaimNextHydratableInstance(workInProgress)
方法嘗試給當(dāng)前 fiber 混合(hydrate)DOM 實(shí)例。如果當(dāng)前 fiber 不能被混合,那當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)在后續(xù)的 render 過程中都不再進(jìn)行hydrate
,而是直接創(chuàng)建 dom 實(shí)例。等到當(dāng)前節(jié)點(diǎn)所有子節(jié)點(diǎn)都調(diào)用completeUnitOfWork
完成工作后,又會從當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)開始嘗試混合。
以下面的 demo 為例
// 服務(wù)端返回的DOM結(jié)構(gòu),這里為了直觀,我格式化了一下,按理服務(wù)端返回的內(nèi)容,是不允許換行或者有空字符串的 <body> <div id="root"> <div id="container"> <h1 id="A"> 1 <div id="A2">A2</div> </h1> <p id="B"> <span id="B1">B1</span> </p> <span id="C">C</span> </div> </div> </body> // 客戶端生成的內(nèi)容 <div id="container"> <div id="A"> 1 <div id="A2">A2</div> </div> <p id="B"> <span id="B1">B1</span> </p> </div>
render 階段,按以下順序:
div#container
滿足hydrate
的條件,因此關(guān)聯(lián) dom,fiber.stateNode = div#container
。然后使用hydrationParentFiber
記錄當(dāng)前混合的 fiber 節(jié)點(diǎn):hydrationParentFiber = fiber
。獲取下一個 DOM 實(shí)例,這里是h1#A
,保存在變量nextHydratableInstance
中,nextHydratableInstance = h1#A
。
這里,hydrationParentFiber
和 nextHydratableInstance
都是全局變量。
-
div#A
和h1#A
不能混合,這時并不會立即結(jié)束混合的過程,React 繼續(xù)對比h1#A
的兄弟節(jié)點(diǎn),即p#B
,發(fā)現(xiàn)div#A
還是不能和p#B
混合,經(jīng)過最多兩次對比,React 認(rèn)為 dom 樹中已經(jīng)沒有 dom 實(shí)例滿足和div#A
這個 fiber 混合的條件,于是div#A
節(jié)點(diǎn)及其所有子孫節(jié)點(diǎn)都不再進(jìn)行混合的過程,此時將isHydrating
設(shè)置為 false 表明div#A
這棵子樹都不再走混合的過程,直接走創(chuàng)建 dom 實(shí)例。同時控制臺提示:Expected server HTML to contain a matching..
之類的錯誤。 - beginWork 執(zhí)行到文本節(jié)點(diǎn)
1
時,發(fā)現(xiàn)isHydrating = false
,因此直接跳過混合的過程,在completeUnitOfWork
階段直接調(diào)用document.createTextNode
直接為其創(chuàng)建文本節(jié)點(diǎn) - 同樣的,beginWork 執(zhí)行到節(jié)點(diǎn)
div#A2
時,發(fā)現(xiàn)isHydrating = false
,因此直接跳過混合的過程,在completeUnitOfWork
階段直接調(diào)用document.createElement
直接為其創(chuàng)建真實(shí) dom 實(shí)例,并設(shè)置屬性 - 由于
div#A
的子節(jié)點(diǎn)都已經(jīng)completeUnitWork
了,輪到div#A
調(diào)用completeUnitWork
完成工作,將hydrationParentFiber
指向其父節(jié)點(diǎn),即div#container
這個 dom 實(shí)例。設(shè)置isHydrating = true
表明可以為當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)繼續(xù)混合的過程了。div#A
沒有混合的 dom 實(shí)例,因此調(diào)用document.createElement
為其創(chuàng)建真實(shí)的 dom 實(shí)例。 - 為
p#B
執(zhí)行 beginWork。由于nextHydratableInstance
保存的還是h1#A
dom 實(shí)例,因此p#B
和h1#A
對比發(fā)現(xiàn)不能復(fù)用,React 嘗試和h1#A
的兄弟節(jié)點(diǎn)p#B
對比,發(fā)現(xiàn) fiberp#B
和 domp#B
能混,因此將h1#A
標(biāo)記為刪除,同時關(guān)聯(lián) dom 實(shí)例:fiber.stateNode = p#B
,保存hydrationParentFiber = fiber
,nextHydratableInstance
指向p#B
的第一個子節(jié)點(diǎn),即span#B1
...省略了后續(xù)的過程。
從上面的執(zhí)行過程可以看出,hydrate 的過程如下:
- 調(diào)用
tryToClaimNextHydratableInstance
開始混合 - 判斷當(dāng)前 fiber 節(jié)點(diǎn)和同一位置的 dom 實(shí)例是否滿足混合的條件。
- 如果當(dāng)前位置的 dom 實(shí)例不滿足混合條件,則繼續(xù)比較當(dāng)前 dom 的兄弟元素,如果兄弟元素和當(dāng)前的 fiber 也不能混合,則當(dāng)前 fiber 及其所有子孫節(jié)點(diǎn)都不能混合,后續(xù) render 過程將會跳過混合。直到當(dāng)前 fiber 節(jié)點(diǎn)的兄弟節(jié)點(diǎn) render,才會繼續(xù)混合的過程。
事件綁定
React在初次渲染時,不論是ReactDOM.render
還是ReactDOM.hydrate
,會調(diào)用createRootImpl
函數(shù)創(chuàng)建fiber的容器,在這個函數(shù)中調(diào)用listenToAllSupportedEvents
注冊所有原生的事件。
function createRootImpl(container, tag, options) { // ... var root = createContainer(container, tag, hydrate); // ... listenToAllSupportedEvents(container); // ... return root; }
這里container
就是div#root
節(jié)點(diǎn)。listenToAllSupportedEvents
會給div#root
節(jié)點(diǎn)注冊瀏覽器支持的所有原生事件,比如onclick
等。React合成事件一文介紹過,React采用的是事件委托的機(jī)制,將所有事件代理到div#root
節(jié)點(diǎn)上。以下面的為例:
<div id="A" onClick={this.handleClick}> button <div>
我們知道React在渲染時,會將fiber的props關(guān)聯(lián)到真實(shí)的dom的__reactProps$
屬性上,此時
div#A.__reactProps$ = { onClick: this.handleClick }
當(dāng)我們點(diǎn)擊按鈕時,會觸發(fā)div#root
上的事件監(jiān)聽器:
function onclick(e){ const target = e.target const fiberProps = target.__reactProps$ const clickhandle = fiberProps.onClick if(clickhandle){ clickhandle(e) } }
這樣我們就可以實(shí)現(xiàn)事件的委托。這其中最重要的就是將fiber的props掛載到真實(shí)的dom實(shí)例的__reactProps$屬性上。因此,只要我們在hydrate
階段能夠成功關(guān)聯(lián)dom和fiber,就自然也實(shí)現(xiàn)了事件的“綁定”
hydrate 源碼剖析
hydrate 的過程發(fā)生在 render 階段,commit 階段幾乎沒有和 hydrate 相關(guān)的邏輯。render 階段又分為兩個小階段:beginWork
和 completeUnitOfWork
。只有HostRoot
、HostComponent
、HostText
三種類型的 fiber 節(jié)點(diǎn)才需要 hydrate,因此源碼只針對這三種類型的 fiber 節(jié)點(diǎn)剖析
beginWork
beginWork 階段判斷 fiber 和 dom 實(shí)例是否滿足混合的條件,如果滿足,則為 fiber 關(guān)聯(lián) dom 實(shí)例:fiber.stateNode = dom
function beginWork(current, workInProgress, renderLanes) { switch (workInProgress.tag) { case HostRoot: return updateHostRoot(current, workInProgress, renderLanes); case HostComponent: return updateHostComponent(current, workInProgress, renderLanes); case HostText: return updateHostText(current, workInProgress); } }
HostRoot Fiber
HostRoot
fiber 是容器root
的 fiber 節(jié)點(diǎn)。
這里主要是判斷當(dāng)前 render 是ReactDOM.render
還是ReactDOM.hydrate
,我們調(diào)用ReactDOM.hydrate
渲染時,root.hydrate
為 true。
如果是調(diào)用的ReactDOM.hydrate
,則調(diào)用enterHydrationState
函數(shù)進(jìn)入hydrate
的過程。這個函數(shù)主要是初始化幾個全局變量:
- isHydrating。表示當(dāng)前正處于 hydrate 的過程。如果當(dāng)前節(jié)點(diǎn)及其所有子孫節(jié)點(diǎn)都不滿足 hydrate 的條件時,這個變量為 false
- hydrationParentFiber。當(dāng)前混合的 fiber。正常情況下,該變量和
HostComponent
或者HostText
類型的 workInProgress 一致。 - nextHydratableInstance。下一個可以混合的 dom 實(shí)例。當(dāng)前 dom 實(shí)例的第一個子元素或者兄弟元素。
注意getNextHydratable
會判斷 dom 實(shí)例是否是ELEMENT_NODE
類型(對應(yīng)的 fiber 類型是HostComponent
)或者TEXT_NODE
類型(對應(yīng)的 fiber 類型是HostText
)。只有ELEMENT_NODE
或者HostText
類型的 dom 實(shí)例才是可以 hydrate 的
function updateHostRoot(current, workInProgress, renderLanes) { if (root.hydrate && enterHydrationState(workInProgress)) { var child = mountChildFibers(workInProgress, null, nextChildren); } return workInProgress.child; } function getNextHydratable(node) { // 跳過 non-hydratable 節(jié)點(diǎn). for (; node != null; node = node.nextSibling) { var nodeType = node.nodeType; if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) { break; } } return node; } function enterHydrationState() { var parentInstance = fiber.stateNode.containerInfo; nextHydratableInstance = getNextHydratable(parentInstance.firstChild); hydrationParentFiber = fiber; isHydrating = true; }
HostComponent
function updateHostComponent(current, workInProgress, renderLanes) { if (current === null) { tryToClaimNextHydratableInstance(workInProgress); } reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; }
HostText Fiber
function updateHostText(current, workInProgress) { if (current === null) { tryToClaimNextHydratableInstance(workInProgress); } return null; }
tryToClaimNextHydratableInstance
假設(shè)當(dāng)前 fiberA 對應(yīng)位置的 dom 為 domA,tryToClaimNextHydratableInstance
會首先調(diào)用tryHydrate
判斷 fiberA 和 domA 是否滿足混合的條件:
如果 fiberA 和 domA 滿足混合的條件,則將hydrationParentFiber = fiberA;
。并且獲取 domA 的第一個子元素賦值給nextHydratableInstance
如果 fiberA 和 domA 不滿足混合的條件,則獲取 domA 的兄弟節(jié)點(diǎn),即 domB,調(diào)用tryHydrate
判斷 fiberA 和 domB 是否滿足混合條件:
- 如果 domB 滿足和 fiberA 混合的條件,則將 domA 標(biāo)記為刪除,并獲取 domB 的第一個子元素賦值給
nextHydratableInstance
- 如果 domB 不滿足和 fiberA 混合的條件,則調(diào)用
insertNonHydratedInstance
提示錯誤:"Warning: Expected server HTML to contain a matching",同時將isHydrating
標(biāo)記為 false 退出。
這里可以看出,tryToClaimNextHydratableInstance
最多比較兩個 dom 節(jié)點(diǎn),如果兩個 dom 節(jié)點(diǎn)都無法滿足和 fiberA 混合的條件,則說明當(dāng)前 fiberA 及其所有的子孫節(jié)點(diǎn)都無需再進(jìn)行混合的過程,因此將isHydrating
標(biāo)記為 false。等到當(dāng)前 fiberA 節(jié)點(diǎn)及其子節(jié)點(diǎn)都完成了工作,即都執(zhí)行了completeWork
,isHydrating
才會被設(shè)置為 true,以便繼續(xù)比較 fiberA 的兄弟節(jié)點(diǎn)
這里還需要注意一點(diǎn),如果兩個 dom 都無法滿足和 fiberA 混合,那么nextHydratableInstance
依然保存的是 domA,domA 會繼續(xù)和 fiberA 的兄弟節(jié)點(diǎn)比對。
function tryToClaimNextHydratableInstance(fiber) { if (!isHydrating) { return; } var nextInstance = nextHydratableInstance; var firstAttemptedInstance = nextInstance; if (!tryHydrate(fiber, nextInstance)) { // 如果第一次調(diào)用tryHydrate發(fā)現(xiàn)當(dāng)前fiber和dom不滿足hydrate的條件,則獲取dom的兄弟節(jié)點(diǎn) // 然后調(diào)用 tryHydrate 繼續(xù)對比fiber和兄弟節(jié)點(diǎn)是否滿足混合 nextInstance = getNextHydratableSibling(firstAttemptedInstance); if (!nextInstance || !tryHydrate(fiber, nextInstance)) { // 對比了兩個dom發(fā)現(xiàn)都無法和fiber混合,因此調(diào)用insertNonHydratedInstance控制臺提示錯誤 insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; return; } // 如果第一次tryHydrate不滿足,第二次tryHydrate滿足,則說明兄弟節(jié)點(diǎn)和當(dāng)前fiber是可以混合的,此時需要刪除當(dāng)前位置的dom deleteHydratableInstance(hydrationParentFiber, firstAttemptedInstance); } hydrationParentFiber = fiber; nextHydratableInstance = getFirstHydratableChild(nextInstance); } // 將dom實(shí)例保存在 fiber.stateNode上 function tryHydrate(fiber, nextInstance) { switch (fiber.tag) { case HostComponent: { if ( nextInstance.nodeType === ELEMENT_NODE && fiber.type.toLowerCase() === nextInstance.nodeName.toLowerCase() ) { fiber.stateNode = nextInstance; return true; } return false; } case HostText: { var text = fiber.pendingProps; if (text !== "" && nextInstance.nodeType === TEXT_NODE) { fiber.stateNode = nextInstance; return true; } return false; } default: return false; } }
completeUnitOfWork
completeUnitOfWork 階段主要是給 dom 關(guān)聯(lián) fiber 以及 props:dom.__reactProps$ = fiber.pendingProps;dom.__reactFiber$ = fiber;
同時對比fiber.pendingProps
和dom.attributes
的差異
function completeUnitOfWork(unitOfWork) { var completedWork = unitOfWork; do { var current = completedWork.alternate; var returnFiber = completedWork.return; next = completeWork(current, completedWork, subtreeRenderLanes); var siblingFiber = completedWork.sibling; if (siblingFiber !== null) { workInProgress = siblingFiber; return; } completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null); } function completeWork(current, workInProgress, renderLanes) { switch (workInProgress.tag) { case HostRoot: { if (current === null) { var wasHydrated = popHydrationState(workInProgress); if (wasHydrated) { markUpdate(workInProgress); } } return null; } case HostComponent: // 第一次渲染 if (current === null) { var _wasHydrated = popHydrationState(workInProgress); if (_wasHydrated) { // 如果存在差異的屬性,則將fiber副作用標(biāo)記為更新 if (prepareToHydrateHostInstance(workInProgress)) { markUpdate(workInProgress); } } else { } } case HostText: { var newText = newProps; if (current === null) { var _wasHydrated2 = popHydrationState(workInProgress); if (_wasHydrated2) { if (prepareToHydrateHostTextInstance(workInProgress)) { markUpdate(workInProgress); } } } return null; } } }
popHydrationState
function popHydrationState(fiber) { if (fiber !== hydrationParentFiber) { return false; } if (!isHydrating) { popToNextHostParent(fiber); isHydrating = true; return false; } var type = fiber.type; if ( fiber.tag !== HostComponent || !shouldSetTextContent(type, fiber.memoizedProps) ) { var nextInstance = nextHydratableInstance; while (nextInstance) { deleteHydratableInstance(fiber, nextInstance); nextInstance = getNextHydratableSibling(nextInstance); } } popToNextHostParent(fiber); nextHydratableInstance = hydrationParentFiber ? getNextHydratableSibling(fiber.stateNode) : null; return true; }
以下圖為例:
在 beginWork 階段對 p#B
fiber 工作時,發(fā)現(xiàn) dom 樹中同一位置的h1#B
不滿足混合的條件,于是繼續(xù)對比h1#B
的兄弟節(jié)點(diǎn),即div#C
,仍然無法混合,經(jīng)過最多兩輪對比后發(fā)現(xiàn)p#B
這個 fiber 沒有可以混合的 dom 節(jié)點(diǎn),于是將 isHydrating
標(biāo)記為 false,hydrationParentFiber = fiberP#B
。p#B
的子孫節(jié)點(diǎn)都不再進(jìn)行混合的過程。
div#B1
fiber 沒有子節(jié)點(diǎn),因此它可以調(diào)用completeUnitOfWork
完成工作,completeUnitOfWork
階段調(diào)用 popHydrationState
方法,在popHydrationState
方法內(nèi)部,首先判斷 fiber !== hydrationParentFiber
,由于此時的hydrationParentFiber
等于p#B
,因此條件成立,不用往下執(zhí)行。
由于p#B
fiber 的子節(jié)點(diǎn)都已經(jīng)完成了工作,因此它也可以調(diào)用completeUnitOfWork
完成工作。同樣的,在popHydrationState
函數(shù)內(nèi)部,第一個判斷fiber !== hydrationParentFiber
不成立,兩者是相等的。第二個條件!isHydrating
成立,進(jìn)入條件語句,首先調(diào)用popToNextHostParent
將hydrationParentFiber
設(shè)置為p#B
的第一個類型為HostComponent
的祖先元素,這里是div#A
fiber,然后將isHydrating
設(shè)置為 true,指示可以為p#B
的兄弟節(jié)點(diǎn)進(jìn)行混合。
如果服務(wù)端返回的 DOM 有多余的情況,則調(diào)用deleteHydratableInstance
將其刪除,比如下圖中div#D
節(jié)點(diǎn)將會在div#A
fiber 的completeUnitOfWork
階段刪除
prepareToHydrateHostInstance
對于HostComponent
類型的fiber會調(diào)用這個方法,這里只要是關(guān)聯(lián) dom 和 fiber:
- 設(shè)置
domInstance.__reactFiber$w63z5ormsqk = fiber
- 設(shè)置
domInstance.__reactProps$w63z5ormsqk = props
- 對比服務(wù)端和客戶端的屬性
function prepareToHydrateHostInstance(fiber) { var domInstance = fiber.stateNode; var updatePayload = hydrateInstance( domInstance, fiber.type, fiber.memoizedProps, fiber ); fiber.updateQueue = updatePayload; if (updatePayload !== null) { return true; } return false; } function hydrateInstance(domInstance, type, props, fiber) { precacheFiberNode(fiber, domInstance); // domInstance.__reactFiber$w63z5ormsqk = fiber updateFiberProps(domInstance, props); // domInstance.__reactProps$w63z5ormsqk = props // 比較dom.attributes和props的差異,如果dom.attributes的屬性比props多,說明服務(wù)端添加了額外的屬性,此時控制臺提示。 // 注意,在對比過程中,只有服務(wù)端和客戶端的children屬性(即文本內(nèi)容)不同時,控制臺才會提示錯誤,同時在commit階段,客戶端會糾正這個錯誤,以客戶端的文本為主。 // 但是,如果是id不同,則客戶端并不會糾正。 return diffHydratedProperties(domInstance, type, props); }
這里重點(diǎn)講下diffHydratedProperties
,以下面的demo為例:
// 服務(wù)端對應(yīng)的dom <div id="root"><div extra="server attr" id="server">客戶端的文本</div></div> // 客戶端 render() { const { count } = this.state; return <div id="client">客戶端的文本</div>; }
在diffHydratedProperties
的過程中發(fā)現(xiàn),服務(wù)端返回的id和客戶端的id不同,控制臺提示id不匹配,但是客戶端并不會糾正這個,可以看到瀏覽器的id依然是server
。
同時,服務(wù)端多返回了一個extra
屬性,因此需要控制臺提示,但由于已經(jīng)提示了id不同的錯誤,這個錯誤就不會提示。
最后,客戶端的文本和服務(wù)端的children不同,即文本內(nèi)容不同,也需要提示錯誤,同時,客戶端會糾正這個文本,以客戶端的為主。
prepareToHydrateHostTextInstance
對于HostText
類型的fiber會調(diào)用這個方法,這個方法邏輯比較簡單,就不詳細(xì)介紹了 務(wù)端對應(yīng)的dom
<div id="root"><div extra="server attr" id="server">客戶端的文本</div></div> // 客戶端 render() { const { count } = this.state; return <div id="client">客戶端的文本</div>; }
在diffHydratedProperties
的過程中發(fā)現(xiàn),服務(wù)端返回的id和客戶端的id不同,控制臺提示id不匹配,但是客戶端并不會糾正這個,可以看到瀏覽器的id依然是server
。
同時,服務(wù)端多返回了一個extra
屬性,因此需要控制臺提示,但由于已經(jīng)提示了id不同的錯誤,這個錯誤就不會提示。
最后,客戶端的文本和服務(wù)端的children不同,即文本內(nèi)容不同,也需要提示錯誤,同時,客戶端會糾正這個文本,以客戶端的為主。
prepareToHydrateHostTextInstance
對于HostText
類型的fiber會調(diào)用這個方法,這個方法邏輯比較簡單,就不詳細(xì)介紹了
原文鏈接:https://juejin.cn/post/7184966395762278437
相關(guān)推薦
- 2022-06-12 C語言詳解float類型在內(nèi)存中的存儲方式_C 語言
- 2022-05-11 Excel表中數(shù)據(jù)轉(zhuǎn)為sql
- 2022-06-02 Pandas實(shí)現(xiàn)DataFrame的簡單運(yùn)算、統(tǒng)計與排序_python
- 2023-04-24 Python?語法錯誤:"SyntaxError:?invalid?character?in?ide
- 2024-03-03 layui 表格select下拉不顯示全的問題
- 2023-07-15 前后端交互,前端參數(shù)格式form-data
- 2022-07-09 python?監(jiān)控某個進(jìn)程內(nèi)存的情況問題_python
- 2022-11-20 Golang交叉編譯之跨平臺編譯使用詳解_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支