React?Hydrate原理源碼解析
引言
React 渲染過程,即ReactDOM.render
執(zhí)行過程分為兩個大的階段:render
階段以及 commit
階段。React.hydrate
渲染過程和ReactDOM.render
差不多,兩者之間最大的區(qū)別就是,ReactDOM.hydrate
在 render
階段,會嘗試復用(hydrate)瀏覽器現(xiàn)有的 dom 節(jié)點,并相互關聯(lián) dom 實例和 fiber,以及找出 dom 屬性和 fiber 屬性之間的差異。
Demo
這里,我們在 index.html
中直接返回一段 html,以模擬服務端渲染生成的 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
里面的內容不能換行,不然客戶端hydrate
的時候會提示服務端和客戶端的模版不一致。
新建 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"));
對比服務端和客戶端的內容可知,服務端h1#A
和客戶端的div#A
不同,同時服務端比客戶端多了一個span#C
在客戶端開始執(zhí)行之前,即 ReactDOM.hydrate
開始執(zhí)行前,由于服務端已經返回了 html 內容,瀏覽器會立馬顯示內容。對應的真實 DOM 樹如下:
注意,這不是 fiber 樹??!
ReactDOM.render
先來回顧一下 React 渲染更新過程,分為兩大階段,五小階段:
render 階段
- beginWork
- completeUnitOfWork
commit 階段。
- commitBeforeMutationEffects
- commitMutationEffects
- commitLayoutEffects
React 在 render 階段會根據新的 element tree 構建 workInProgress 樹,收集具有副作用的 fiber 節(jié)點,構建副作用鏈表。
特別是,當我們調用ReactDOM.render
函數在客戶端進行第一次渲染時,render
階段的completeUnitOfWork
函數針對HostComponent
以及HostText
類型的 fiber 執(zhí)行以下 dom 相關的操作:
- 調用
document.createElement
為HostComponent
類型的 fiber 節(jié)點創(chuàng)建真實的 DOM 實例?;蛘哒{用document.createTextNode
為HostText
類型的 fiber 節(jié)點創(chuàng)建真實的 DOM 實例 - 將 fiber 節(jié)點關聯(lián)到真實 dom 的
__reactFiber$rsdw3t27flk
(后面是隨機數)屬性上。 - 將 fiber 節(jié)點的
pendingProps
屬性關聯(lián)到真實 dom 的__reactProps$rsdw3t27flk
(后面是隨機數)屬性上 - 將真實的 dom 實例關聯(lián)到
fiber.stateNode
屬性上:fiber.stateNode = dom
。 - 遍歷
pendingProps
,給真實的dom
設置屬性,比如設置 id、textContent 等
React 渲染更新完成后,React 會為每個真實的 dom 實例掛載兩個私有的屬性:__reactFiber$
和__reactProps$
,以div#container
為例:
ReactDOM.hydrate
hydrate
中文意思是水合物,這樣理解有點抽象。根據源碼,我更樂意將hydrate
的過程描述為:React 在 render 階段,構造 workInProgress 樹時,同時按相同的順序遍歷真實的 DOM 樹,判斷當前的 workInProgress fiber 節(jié)點和同一位置的 dom 實例是否滿足hydrate
的條件,如果滿足,則直接復用當前位置的 DOM 實例,并相互關聯(lián) workInProgress fiber 節(jié)點和真實的 dom 實例,比如:
fiber.stateNode = dom; dom.__reactProps$ = fiber.pendingProps; dom.__reactFiber$ = fiber;
如果 fiber 和 dom 滿足hydrate
的條件,則還需要找出dom.attributes
和fiber.pendingProps
之間的屬性差異。
遍歷真實 DOM 樹的順序和構建 workInProgress 樹的順序是一致的。都是深度優(yōu)先遍歷,先遍歷當前節(jié)點的子節(jié)點,子節(jié)點都遍歷完了以后,再遍歷當前節(jié)點的兄弟節(jié)點。因為只有按相同的順序,fiber 樹同一位置的 fiber 節(jié)點和 dom 樹同一位置的 dom 節(jié)點才能保持一致
只有類型為HostComponent
或者HostText
類型的 fiber 節(jié)點才能hydrate
。這一點也很好理解,React 在 commit 階段,也就只有這兩個類型的 fiber 節(jié)點才需要執(zhí)行 dom 操作。
fiber 節(jié)點和 dom 實例是否滿足hydrate
的條件:
- 對于類型為
HostComponent
的 fiber 節(jié)點,如果當前位置對應的 DOM 實例nodeType
為ELEMENT_NODE
,并且fiber.type === dom.nodeName
,那么當前的 fiber 可以混合(hydrate) - 對于類型為
HostText
的 fiber 節(jié)點,如果當前位置對應的 DOM 實例nodeType
為TEXT_NODE
,同時fiber.pendingProps
不為空,那么當前的 fiber 可以混合(hydrate)
hydrate
的終極目標就是,在構造 workInProgress 樹的過程中,盡可能的復用當前瀏覽器已經存在的 DOM 實例以及 DOM 上的屬性,這樣就無需再為 fiber 節(jié)點創(chuàng)建 DOM 實例,同時對比現(xiàn)有的 DOM 的attribute
以及 fiber 的pendingProps
,找出差異的屬性。然后將 dom 實例和 fiber 節(jié)點相互關聯(lián)(通過 dom 實例的__reactFiber$
以及__reactProps$
,fiber 的 stateNode 相互關聯(lián))
hydrate 過程
React 在 render 階段構造HostComponent
或者HostText
類型的 fiber 節(jié)點時,會首先調用 tryToClaimNextHydratableInstance(workInProgress)
方法嘗試給當前 fiber 混合(hydrate)DOM 實例。如果當前 fiber 不能被混合,那當前節(jié)點的所有子節(jié)點在后續(xù)的 render 過程中都不再進行hydrate
,而是直接創(chuàng)建 dom 實例。等到當前節(jié)點所有子節(jié)點都調用completeUnitOfWork
完成工作后,又會從當前節(jié)點的兄弟節(jié)點開始嘗試混合。
以下面的 demo 為例
// 服務端返回的DOM結構,這里為了直觀,我格式化了一下,按理服務端返回的內容,是不允許換行或者有空字符串的 <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> // 客戶端生成的內容 <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
的條件,因此關聯(lián) dom,fiber.stateNode = div#container
。然后使用hydrationParentFiber
記錄當前混合的 fiber 節(jié)點:hydrationParentFiber = fiber
。獲取下一個 DOM 實例,這里是h1#A
,保存在變量nextHydratableInstance
中,nextHydratableInstance = h1#A
。
這里,hydrationParentFiber
和 nextHydratableInstance
都是全局變量。
div#A
和h1#A
不能混合,這時并不會立即結束混合的過程,React 繼續(xù)對比h1#A
的兄弟節(jié)點,即p#B
,發(fā)現(xiàn)div#A
還是不能和p#B
混合,經過最多兩次對比,React 認為 dom 樹中已經沒有 dom 實例滿足和div#A
這個 fiber 混合的條件,于是div#A
節(jié)點及其所有子孫節(jié)點都不再進行混合的過程,此時將isHydrating
設置為 false 表明div#A
這棵子樹都不再走混合的過程,直接走創(chuàng)建 dom 實例。同時控制臺提示:Expected server HTML to contain a matching..
之類的錯誤。- beginWork 執(zhí)行到文本節(jié)點
1
時,發(fā)現(xiàn)isHydrating = false
,因此直接跳過混合的過程,在completeUnitOfWork
階段直接調用document.createTextNode
直接為其創(chuàng)建文本節(jié)點 - 同樣的,beginWork 執(zhí)行到節(jié)點
div#A2
時,發(fā)現(xiàn)isHydrating = false
,因此直接跳過混合的過程,在completeUnitOfWork
階段直接調用document.createElement
直接為其創(chuàng)建真實 dom 實例,并設置屬性 - 由于
div#A
的子節(jié)點都已經completeUnitWork
了,輪到div#A
調用completeUnitWork
完成工作,將hydrationParentFiber
指向其父節(jié)點,即div#container
這個 dom 實例。設置isHydrating = true
表明可以為當前節(jié)點的兄弟節(jié)點繼續(xù)混合的過程了。div#A
沒有混合的 dom 實例,因此調用document.createElement
為其創(chuàng)建真實的 dom 實例。 - 為
p#B
執(zhí)行 beginWork。由于nextHydratableInstance
保存的還是h1#A
dom 實例,因此p#B
和h1#A
對比發(fā)現(xiàn)不能復用,React 嘗試和h1#A
的兄弟節(jié)點p#B
對比,發(fā)現(xiàn) fiberp#B
和 domp#B
能混,因此將h1#A
標記為刪除,同時關聯(lián) dom 實例:fiber.stateNode = p#B
,保存hydrationParentFiber = fiber
,nextHydratableInstance
指向p#B
的第一個子節(jié)點,即span#B1
...省略了后續(xù)的過程。
從上面的執(zhí)行過程可以看出,hydrate 的過程如下:
- 調用
tryToClaimNextHydratableInstance
開始混合 - 判斷當前 fiber 節(jié)點和同一位置的 dom 實例是否滿足混合的條件。
- 如果當前位置的 dom 實例不滿足混合條件,則繼續(xù)比較當前 dom 的兄弟元素,如果兄弟元素和當前的 fiber 也不能混合,則當前 fiber 及其所有子孫節(jié)點都不能混合,后續(xù) render 過程將會跳過混合。直到當前 fiber 節(jié)點的兄弟節(jié)點 render,才會繼續(xù)混合的過程。
事件綁定
React在初次渲染時,不論是ReactDOM.render
還是ReactDOM.hydrate
,會調用createRootImpl
函數創(chuàng)建fiber的容器,在這個函數中調用listenToAllSupportedEvents
注冊所有原生的事件。
function createRootImpl(container, tag, options) { // ... var root = createContainer(container, tag, hydrate); // ... listenToAllSupportedEvents(container); // ... return root; }
這里container
就是div#root
節(jié)點。listenToAllSupportedEvents
會給div#root
節(jié)點注冊瀏覽器支持的所有原生事件,比如onclick
等。React合成事件一文介紹過,React采用的是事件委托的機制,將所有事件代理到div#root
節(jié)點上。以下面的為例:
<div id="A" onClick={this.handleClick}> button <div>
我們知道React在渲染時,會將fiber的props關聯(lián)到真實的dom的__reactProps$
屬性上,此時
div#A.__reactProps$ = { onClick: this.handleClick }
當我們點擊按鈕時,會觸發(fā)div#root
上的事件監(jiān)聽器:
function onclick(e){ const target = e.target const fiberProps = target.__reactProps$ const clickhandle = fiberProps.onClick if(clickhandle){ clickhandle(e) } }
這樣我們就可以實現(xiàn)事件的委托。這其中最重要的就是將fiber的props掛載到真實的dom實例的__reactProps$屬性上。因此,只要我們在hydrate
階段能夠成功關聯(lián)dom和fiber,就自然也實現(xiàn)了事件的“綁定”
hydrate 源碼剖析
hydrate 的過程發(fā)生在 render 階段,commit 階段幾乎沒有和 hydrate 相關的邏輯。render 階段又分為兩個小階段:beginWork
和 completeUnitOfWork
。只有HostRoot
、HostComponent
、HostText
三種類型的 fiber 節(jié)點才需要 hydrate,因此源碼只針對這三種類型的 fiber 節(jié)點剖析
beginWork
beginWork 階段判斷 fiber 和 dom 實例是否滿足混合的條件,如果滿足,則為 fiber 關聯(lián) dom 實例: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é)點。
這里主要是判斷當前 render 是ReactDOM.render
還是ReactDOM.hydrate
,我們調用ReactDOM.hydrate
渲染時,root.hydrate
為 true。
如果是調用的ReactDOM.hydrate
,則調用enterHydrationState
函數進入hydrate
的過程。這個函數主要是初始化幾個全局變量:
- isHydrating。表示當前正處于 hydrate 的過程。如果當前節(jié)點及其所有子孫節(jié)點都不滿足 hydrate 的條件時,這個變量為 false
- hydrationParentFiber。當前混合的 fiber。正常情況下,該變量和
HostComponent
或者HostText
類型的 workInProgress 一致。 - nextHydratableInstance。下一個可以混合的 dom 實例。當前 dom 實例的第一個子元素或者兄弟元素。
注意getNextHydratable
會判斷 dom 實例是否是ELEMENT_NODE
類型(對應的 fiber 類型是HostComponent
)或者TEXT_NODE
類型(對應的 fiber 類型是HostText
)。只有ELEMENT_NODE
或者HostText
類型的 dom 實例才是可以 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é)點. 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
假設當前 fiberA 對應位置的 dom 為 domA,tryToClaimNextHydratableInstance
會首先調用tryHydrate
判斷 fiberA 和 domA 是否滿足混合的條件:
如果 fiberA 和 domA 滿足混合的條件,則將hydrationParentFiber = fiberA;
。并且獲取 domA 的第一個子元素賦值給nextHydratableInstance
如果 fiberA 和 domA 不滿足混合的條件,則獲取 domA 的兄弟節(jié)點,即 domB,調用tryHydrate
判斷 fiberA 和 domB 是否滿足混合條件:
- 如果 domB 滿足和 fiberA 混合的條件,則將 domA 標記為刪除,并獲取 domB 的第一個子元素賦值給
nextHydratableInstance
- 如果 domB 不滿足和 fiberA 混合的條件,則調用
insertNonHydratedInstance
提示錯誤:"Warning: Expected server HTML to contain a matching",同時將isHydrating
標記為 false 退出。
這里可以看出,tryToClaimNextHydratableInstance
最多比較兩個 dom 節(jié)點,如果兩個 dom 節(jié)點都無法滿足和 fiberA 混合的條件,則說明當前 fiberA 及其所有的子孫節(jié)點都無需再進行混合的過程,因此將isHydrating
標記為 false。等到當前 fiberA 節(jié)點及其子節(jié)點都完成了工作,即都執(zhí)行了completeWork
,isHydrating
才會被設置為 true,以便繼續(xù)比較 fiberA 的兄弟節(jié)點
這里還需要注意一點,如果兩個 dom 都無法滿足和 fiberA 混合,那么nextHydratableInstance
依然保存的是 domA,domA 會繼續(xù)和 fiberA 的兄弟節(jié)點比對。
function tryToClaimNextHydratableInstance(fiber) { if (!isHydrating) { return; } var nextInstance = nextHydratableInstance; var firstAttemptedInstance = nextInstance; if (!tryHydrate(fiber, nextInstance)) { // 如果第一次調用tryHydrate發(fā)現(xiàn)當前fiber和dom不滿足hydrate的條件,則獲取dom的兄弟節(jié)點 // 然后調用 tryHydrate 繼續(xù)對比fiber和兄弟節(jié)點是否滿足混合 nextInstance = getNextHydratableSibling(firstAttemptedInstance); if (!nextInstance || !tryHydrate(fiber, nextInstance)) { // 對比了兩個dom發(fā)現(xiàn)都無法和fiber混合,因此調用insertNonHydratedInstance控制臺提示錯誤 insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; return; } // 如果第一次tryHydrate不滿足,第二次tryHydrate滿足,則說明兄弟節(jié)點和當前fiber是可以混合的,此時需要刪除當前位置的dom deleteHydratableInstance(hydrationParentFiber, firstAttemptedInstance); } hydrationParentFiber = fiber; nextHydratableInstance = getFirstHydratableChild(nextInstance); } // 將dom實例保存在 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 關聯(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副作用標記為更新 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é)點,即div#C
,仍然無法混合,經過最多兩輪對比后發(fā)現(xiàn)p#B
這個 fiber 沒有可以混合的 dom 節(jié)點,于是將 isHydrating
標記為 false,hydrationParentFiber = fiberP#B
。p#B
的子孫節(jié)點都不再進行混合的過程。
div#B1
fiber 沒有子節(jié)點,因此它可以調用completeUnitOfWork
完成工作,completeUnitOfWork
階段調用 popHydrationState
方法,在popHydrationState
方法內部,首先判斷 fiber !== hydrationParentFiber
,由于此時的hydrationParentFiber
等于p#B
,因此條件成立,不用往下執(zhí)行。
由于p#B
fiber 的子節(jié)點都已經完成了工作,因此它也可以調用completeUnitOfWork
完成工作。同樣的,在popHydrationState
函數內部,第一個判斷fiber !== hydrationParentFiber
不成立,兩者是相等的。第二個條件!isHydrating
成立,進入條件語句,首先調用popToNextHostParent
將hydrationParentFiber
設置為p#B
的第一個類型為HostComponent
的祖先元素,這里是div#A
fiber,然后將isHydrating
設置為 true,指示可以為p#B
的兄弟節(jié)點進行混合。
如果服務端返回的 DOM 有多余的情況,則調用deleteHydratableInstance
將其刪除,比如下圖中div#D
節(jié)點將會在div#A
fiber 的completeUnitOfWork
階段刪除
prepareToHydrateHostInstance
對于HostComponent
類型的fiber會調用這個方法,這里只要是關聯(lián) dom 和 fiber:
- 設置
domInstance.__reactFiber$w63z5ormsqk = fiber
- 設置
domInstance.__reactProps$w63z5ormsqk = props
- 對比服務端和客戶端的屬性
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多,說明服務端添加了額外的屬性,此時控制臺提示。 // 注意,在對比過程中,只有服務端和客戶端的children屬性(即文本內容)不同時,控制臺才會提示錯誤,同時在commit階段,客戶端會糾正這個錯誤,以客戶端的文本為主。 // 但是,如果是id不同,則客戶端并不會糾正。 return diffHydratedProperties(domInstance, type, props); }
這里重點講下diffHydratedProperties
,以下面的demo為例:
// 服務端對應的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),服務端返回的id和客戶端的id不同,控制臺提示id不匹配,但是客戶端并不會糾正這個,可以看到瀏覽器的id依然是server
。
同時,服務端多返回了一個extra
屬性,因此需要控制臺提示,但由于已經提示了id不同的錯誤,這個錯誤就不會提示。
最后,客戶端的文本和服務端的children不同,即文本內容不同,也需要提示錯誤,同時,客戶端會糾正這個文本,以客戶端的為主。
prepareToHydrateHostTextInstance
對于HostText
類型的fiber會調用這個方法,這個方法邏輯比較簡單,就不詳細介紹了 務端對應的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),服務端返回的id和客戶端的id不同,控制臺提示id不匹配,但是客戶端并不會糾正這個,可以看到瀏覽器的id依然是server
。
同時,服務端多返回了一個extra
屬性,因此需要控制臺提示,但由于已經提示了id不同的錯誤,這個錯誤就不會提示。
最后,客戶端的文本和服務端的children不同,即文本內容不同,也需要提示錯誤,同時,客戶端會糾正這個文本,以客戶端的為主。
prepareToHydrateHostTextInstance
對于HostText
類型的fiber會調用這個方法,這個方法邏輯比較簡單,就不詳細介紹了
以上就是React Hydrate原理源碼解析的詳細內容,更多關于React Hydrate原理的資料請關注腳本之家其它相關文章!
相關文章
淺談React-router v6 實現(xiàn)登錄驗證流程
本文主要介紹了React-router v6 實現(xiàn)登錄驗證流程,主要介紹了公共頁面、受保護頁面和登錄頁面,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-05-05react拖拽組件react-sortable-hoc的使用
本文主要介紹了react拖拽組件react-sortable-hoc的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-02-02React?Native集成支付寶支付的實現(xiàn)方法
這篇文章主要介紹了React?Native集成支付寶支付的實現(xiàn)現(xiàn),ativeModules是JS代碼調用原生模塊的橋梁。所以,我們只需要在原生工程中集成支付寶和微信支付的sdk,然后使用NativeModules調用即可,需要的朋友可以參考下2022-02-02