亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

React?Hydrate原理源碼解析

 更新時(shí)間:2023年01月05日 09:57:04   作者:flyzz177  
這篇文章主要為大家介紹了React?Hydrate原理源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

React 渲染過(guò)程,即ReactDOM.render執(zhí)行過(guò)程分為兩個(gè)大的階段:render 階段以及 commit 階段。React.hydrate渲染過(guò)程和ReactDOM.render差不多,兩者之間最大的區(qū)別就是,ReactDOM.hydraterender 階段,會(huì)嘗試復(fù)用(hydrate)瀏覽器現(xiàn)有的 dom 節(jié)點(diǎn),并相互關(guān)聯(lián) dom 實(shí)例和 fiber,以及找出 dom 屬性和 fiber 屬性之間的差異。

Demo

這里,我們?cè)?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的時(shí)候會(huì)提示服務(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"));

對(duì)比服務(wù)端和客戶端的內(nèi)容可知,服務(wù)端h1#A和客戶端的div#A不同,同時(shí)服務(wù)端比客戶端多了一個(gè)span#C

在客戶端開(kāi)始執(zhí)行之前,即 ReactDOM.hydrate 開(kāi)始執(zhí)行前,由于服務(wù)端已經(jīng)返回了 html 內(nèi)容,瀏覽器會(huì)立馬顯示內(nèi)容。對(duì)應(yīng)的真實(shí) DOM 樹(shù)如下:

注意,這不是 fiber 樹(shù)??!

ReactDOM.render

先來(lái)回顧一下 React 渲染更新過(guò)程,分為兩大階段,五小階段:

render 階段

  • beginWork
  • completeUnitOfWork

commit 階段。

  • commitBeforeMutationEffects
  • commitMutationEffects
  • commitLayoutEffects

React 在 render 階段會(huì)根據(jù)新的 element tree 構(gòu)建 workInProgress 樹(shù),收集具有副作用的 fiber 節(jié)點(diǎn),構(gòu)建副作用鏈表。

特別是,當(dāng)我們調(diào)用ReactDOM.render函數(shù)在客戶端進(jìn)行第一次渲染時(shí),render階段的completeUnitOfWork函數(shù)針對(duì)HostComponent以及HostText類型的 fiber 執(zhí)行以下 dom 相關(guān)的操作:

  • 調(diào)用document.createElementHostComponent類型的 fiber 節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 實(shí)例?;蛘哒{(diào)用document.createTextNodeHostText類型的 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 會(huì)為每個(gè)真實(shí)的 dom 實(shí)例掛載兩個(gè)私有的屬性:__reactFiber$__reactProps$,以div#container為例:

ReactDOM.hydrate

hydrate中文意思是水合物,這樣理解有點(diǎn)抽象。根據(jù)源碼,我更樂(lè)意將hydrate的過(guò)程描述為:React 在 render 階段,構(gòu)造 workInProgress 樹(shù)時(shí),同時(shí)按相同的順序遍歷真實(shí)的 DOM 樹(shù),判斷當(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.attributesfiber.pendingProps之間的屬性差異。

遍歷真實(shí) DOM 樹(shù)的順序和構(gòu)建 workInProgress 樹(shù)的順序是一致的。都是深度優(yōu)先遍歷,先遍歷當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),子節(jié)點(diǎn)都遍歷完了以后,再遍歷當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)。因?yàn)橹挥邪聪嗤捻樞?,fiber 樹(shù)同一位置的 fiber 節(jié)點(diǎn)和 dom 樹(shù)同一位置的 dom 節(jié)點(diǎn)才能保持一致

只有類型為HostComponent或者HostText類型的 fiber 節(jié)點(diǎn)才能hydrate。這一點(diǎn)也很好理解,React 在 commit 階段,也就只有這兩個(gè)類型的 fiber 節(jié)點(diǎn)才需要執(zhí)行 dom 操作。

fiber 節(jié)點(diǎn)和 dom 實(shí)例是否滿足hydrate的條件:

  • 對(duì)于類型為HostComponent的 fiber 節(jié)點(diǎn),如果當(dāng)前位置對(duì)應(yīng)的 DOM 實(shí)例nodeTypeELEMENT_NODE,并且fiber.type === dom.nodeName,那么當(dāng)前的 fiber 可以混合(hydrate)
  • 對(duì)于類型為HostText的 fiber 節(jié)點(diǎn),如果當(dāng)前位置對(duì)應(yīng)的 DOM 實(shí)例nodeTypeTEXT_NODE,同時(shí)fiber.pendingProps不為空,那么當(dāng)前的 fiber 可以混合(hydrate)

hydrate的終極目標(biāo)就是,在構(gòu)造 workInProgress 樹(shù)的過(guò)程中,盡可能的復(fù)用當(dāng)前瀏覽器已經(jīng)存在的 DOM 實(shí)例以及 DOM 上的屬性,這樣就無(wú)需再為 fiber 節(jié)點(diǎn)創(chuàng)建 DOM 實(shí)例,同時(shí)對(duì)比現(xiàn)有的 DOM 的attribute以及 fiber 的pendingProps,找出差異的屬性。然后將 dom 實(shí)例和 fiber 節(jié)點(diǎn)相互關(guān)聯(lián)(通過(guò) dom 實(shí)例的__reactFiber$以及__reactProps$,fiber 的 stateNode 相互關(guān)聯(lián))

hydrate 過(guò)程

React 在 render 階段構(gòu)造HostComponent或者HostText類型的 fiber 節(jié)點(diǎn)時(shí),會(huì)首先調(diào)用 tryToClaimNextHydratableInstance(workInProgress) 方法嘗試給當(dāng)前 fiber 混合(hydrate)DOM 實(shí)例。如果當(dāng)前 fiber 不能被混合,那當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)在后續(xù)的 render 過(guò)程中都不再進(jìn)行hydrate,而是直接創(chuàng)建 dom 實(shí)例。等到當(dāng)前節(jié)點(diǎn)所有子節(jié)點(diǎn)都調(diào)用completeUnitOfWork完成工作后,又會(huì)從當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)開(kāi)始嘗試混合。

以下面的 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。獲取下一個(gè) DOM 實(shí)例,這里是h1#A,保存在變量nextHydratableInstance中,nextHydratableInstance = h1#A。

這里,hydrationParentFibernextHydratableInstance 都是全局變量。

  • div#Ah1#A 不能混合,這時(shí)并不會(huì)立即結(jié)束混合的過(guò)程,React 繼續(xù)對(duì)比h1#A的兄弟節(jié)點(diǎn),即p#B,發(fā)現(xiàn)div#A還是不能和p#B混合,經(jīng)過(guò)最多兩次對(duì)比,React 認(rèn)為 dom 樹(shù)中已經(jīng)沒(méi)有 dom 實(shí)例滿足和div#A這個(gè) fiber 混合的條件,于是div#A節(jié)點(diǎn)及其所有子孫節(jié)點(diǎn)都不再進(jìn)行混合的過(guò)程,此時(shí)將isHydrating設(shè)置為 false 表明div#A這棵子樹(shù)都不再走混合的過(guò)程,直接走創(chuàng)建 dom 實(shí)例。同時(shí)控制臺(tái)提示:Expected server HTML to contain a matching.. 之類的錯(cuò)誤。
  • beginWork 執(zhí)行到文本節(jié)點(diǎn) 1 時(shí),發(fā)現(xiàn) isHydrating = false,因此直接跳過(guò)混合的過(guò)程,在completeUnitOfWork階段直接調(diào)用document.createTextNode直接為其創(chuàng)建文本節(jié)點(diǎn)
  • 同樣的,beginWork 執(zhí)行到節(jié)點(diǎn)div#A2時(shí),發(fā)現(xiàn)isHydrating = false,因此直接跳過(guò)混合的過(guò)程,在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這個(gè) dom 實(shí)例。設(shè)置isHydrating = true表明可以為當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)繼續(xù)混合的過(guò)程了。div#A沒(méi)有混合的 dom 實(shí)例,因此調(diào)用document.createElement為其創(chuàng)建真實(shí)的 dom 實(shí)例。
  • p#B執(zhí)行 beginWork。由于nextHydratableInstance保存的還是h1#Adom 實(shí)例,因此p#Bh1#A對(duì)比發(fā)現(xiàn)不能復(fù)用,React 嘗試和h1#A的兄弟節(jié)點(diǎn)p#B對(duì)比,發(fā)現(xiàn) fiberp#B和 domp#B能混,因此將h1#A標(biāo)記為刪除,同時(shí)關(guān)聯(lián) dom 實(shí)例:fiber.stateNode = p#B,保存hydrationParentFiber = fibernextHydratableInstance指向p#B的第一個(gè)子節(jié)點(diǎn),即span#B1

...省略了后續(xù)的過(guò)程。

從上面的執(zhí)行過(guò)程可以看出,hydrate 的過(guò)程如下:

  • 調(diào)用 tryToClaimNextHydratableInstance 開(kāi)始混合
  • 判斷當(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 過(guò)程將會(huì)跳過(guò)混合。直到當(dāng)前 fiber 節(jié)點(diǎn)的兄弟節(jié)點(diǎn) render,才會(huì)繼續(xù)混合的過(guò)程。

事件綁定

React在初次渲染時(shí),不論是ReactDOM.render還是ReactDOM.hydrate,會(huì)調(diào)用createRootImpl函數(shù)創(chuàng)建fiber的容器,在這個(gè)函數(shù)中調(diào)用listenToAllSupportedEvents注冊(cè)所有原生的事件。

function createRootImpl(container, tag, options) {
  // ...
  var root = createContainer(container, tag, hydrate);
  // ...
  listenToAllSupportedEvents(container);
  // ...
  return root;
}

這里container就是div#root節(jié)點(diǎn)。listenToAllSupportedEvents會(huì)給div#root節(jié)點(diǎn)注冊(cè)瀏覽器支持的所有原生事件,比如onclick等。React合成事件一文介紹過(guò),React采用的是事件委托的機(jī)制,將所有事件代理到div#root節(jié)點(diǎn)上。以下面的為例:

<div id="A" onClick={this.handleClick}>
button
<div>

我們知道React在渲染時(shí),會(huì)將fiber的props關(guān)聯(lián)到真實(shí)的dom的__reactProps$屬性上,此時(shí)

div#A.__reactProps$ = {
  onClick: this.handleClick
}

當(dāng)我們點(diǎn)擊按鈕時(shí),會(huì)觸發(fā)div#root上的事件監(jiān)聽(tīng)器:

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$屬性上。因此,只要我們?cè)?code>hydrate階段能夠成功關(guān)聯(lián)dom和fiber,就自然也實(shí)現(xiàn)了事件的“綁定”

hydrate 源碼剖析

hydrate 的過(guò)程發(fā)生在 render 階段,commit 階段幾乎沒(méi)有和 hydrate 相關(guān)的邏輯。render 階段又分為兩個(gè)小階段:beginWorkcompleteUnitOfWork。只有HostRootHostComponent、HostText三種類型的 fiber 節(jié)點(diǎn)才需要 hydrate,因此源碼只針對(duì)這三種類型的 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渲染時(shí),root.hydrate為 true。

如果是調(diào)用的ReactDOM.hydrate,則調(diào)用enterHydrationState函數(shù)進(jìn)入hydrate的過(guò)程。這個(gè)函數(shù)主要是初始化幾個(gè)全局變量:

  • isHydrating。表示當(dāng)前正處于 hydrate 的過(guò)程。如果當(dāng)前節(jié)點(diǎn)及其所有子孫節(jié)點(diǎn)都不滿足 hydrate 的條件時(shí),這個(gè)變量為 false
  • hydrationParentFiber。當(dāng)前混合的 fiber。正常情況下,該變量和HostComponent或者HostText類型的 workInProgress 一致。
  • nextHydratableInstance。下一個(gè)可以混合的 dom 實(shí)例。當(dāng)前 dom 實(shí)例的第一個(gè)子元素或者兄弟元素。

注意getNextHydratable會(huì)判斷 dom 實(shí)例是否是ELEMENT_NODE類型(對(duì)應(yīng)的 fiber 類型是HostComponent)或者TEXT_NODE類型(對(duì)應(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) {
  // 跳過(guò) 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 對(duì)應(yīng)位置的 dom 為 domA,tryToClaimNextHydratableInstance 會(huì)首先調(diào)用tryHydrate判斷 fiberA 和 domA 是否滿足混合的條件:

如果 fiberA 和 domA 滿足混合的條件,則將hydrationParentFiber = fiberA;。并且獲取 domA 的第一個(gè)子元素賦值給nextHydratableInstance

如果 fiberA 和 domA 不滿足混合的條件,則獲取 domA 的兄弟節(jié)點(diǎn),即 domB,調(diào)用tryHydrate判斷 fiberA 和 domB 是否滿足混合條件:

  • 如果 domB 滿足和 fiberA 混合的條件,則將 domA 標(biāo)記為刪除,并獲取 domB 的第一個(gè)子元素賦值給nextHydratableInstance
  • 如果 domB 不滿足和 fiberA 混合的條件,則調(diào)用insertNonHydratedInstance提示錯(cuò)誤:"Warning: Expected server HTML to contain a matching",同時(shí)將isHydrating標(biāo)記為 false 退出。

這里可以看出,tryToClaimNextHydratableInstance最多比較兩個(gè) dom 節(jié)點(diǎn),如果兩個(gè) dom 節(jié)點(diǎn)都無(wú)法滿足和 fiberA 混合的條件,則說(shuō)明當(dāng)前 fiberA 及其所有的子孫節(jié)點(diǎn)都無(wú)需再進(jìn)行混合的過(guò)程,因此將isHydrating標(biāo)記為 false。等到當(dāng)前 fiberA 節(jié)點(diǎn)及其子節(jié)點(diǎn)都完成了工作,即都執(zhí)行了completeWork,isHydrating才會(huì)被設(shè)置為 true,以便繼續(xù)比較 fiberA 的兄弟節(jié)點(diǎn)

這里還需要注意一點(diǎn),如果兩個(gè) dom 都無(wú)法滿足和 fiberA 混合,那么nextHydratableInstance依然保存的是 domA,domA 會(huì)繼續(xù)和 fiberA 的兄弟節(jié)點(diǎn)比對(duì)。

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ù)對(duì)比f(wàn)iber和兄弟節(jié)點(diǎn)是否滿足混合
    nextInstance = getNextHydratableSibling(firstAttemptedInstance);
    if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
      // 對(duì)比了兩個(gè)dom發(fā)現(xiàn)都無(wú)法和fiber混合,因此調(diào)用insertNonHydratedInstance控制臺(tái)提示錯(cuò)誤
      insertNonHydratedInstance(hydrationParentFiber, fiber);
      isHydrating = false;
      hydrationParentFiber = fiber;
      return;
    }
    // 如果第一次tryHydrate不滿足,第二次tryHydrate滿足,則說(shuō)明兄弟節(jié)點(diǎn)和當(dāng)前fiber是可以混合的,此時(shí)需要?jiǎng)h除當(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;同時(shí)對(duì)比fiber.pendingPropsdom.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 階段對(duì) p#B fiber 工作時(shí),發(fā)現(xiàn) dom 樹(shù)中同一位置的h1#B不滿足混合的條件,于是繼續(xù)對(duì)比h1#B的兄弟節(jié)點(diǎn),即div#C,仍然無(wú)法混合,經(jīng)過(guò)最多兩輪對(duì)比后發(fā)現(xiàn)p#B這個(gè) fiber 沒(méi)有可以混合的 dom 節(jié)點(diǎn),于是將 isHydrating 標(biāo)記為 false,hydrationParentFiber = fiberP#Bp#B的子孫節(jié)點(diǎn)都不再進(jìn)行混合的過(guò)程。

div#B1fiber 沒(méi)有子節(jié)點(diǎn),因此它可以調(diào)用completeUnitOfWork完成工作,completeUnitOfWork 階段調(diào)用 popHydrationState 方法,在popHydrationState方法內(nèi)部,首先判斷 fiber !== hydrationParentFiber,由于此時(shí)的hydrationParentFiber等于p#B,因此條件成立,不用往下執(zhí)行。

由于p#B fiber 的子節(jié)點(diǎn)都已經(jīng)完成了工作,因此它也可以調(diào)用completeUnitOfWork完成工作。同樣的,在popHydrationState函數(shù)內(nèi)部,第一個(gè)判斷fiber !== hydrationParentFiber不成立,兩者是相等的。第二個(gè)條件!isHydrating成立,進(jìn)入條件語(yǔ)句,首先調(diào)用popToNextHostParenthydrationParentFiber設(shè)置為p#B的第一個(gè)類型為HostComponent的祖先元素,這里是div#A fiber,然后將isHydrating設(shè)置為 true,指示可以為p#B的兄弟節(jié)點(diǎn)進(jìn)行混合。

如果服務(wù)端返回的 DOM 有多余的情況,則調(diào)用deleteHydratableInstance將其刪除,比如下圖中div#D節(jié)點(diǎn)將會(huì)在div#Afiber 的completeUnitOfWork階段刪除

prepareToHydrateHostInstance

對(duì)于HostComponent類型的fiber會(huì)調(diào)用這個(gè)方法,這里只要是關(guān)聯(lián) dom 和 fiber:

  • 設(shè)置domInstance.__reactFiber$w63z5ormsqk = fiber
  • 設(shè)置domInstance.__reactProps$w63z5ormsqk = props
  • 對(duì)比服務(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多,說(shuō)明服務(wù)端添加了額外的屬性,此時(shí)控制臺(tái)提示。
  // 注意,在對(duì)比過(guò)程中,只有服務(wù)端和客戶端的children屬性(即文本內(nèi)容)不同時(shí),控制臺(tái)才會(huì)提示錯(cuò)誤,同時(shí)在commit階段,客戶端會(huì)糾正這個(gè)錯(cuò)誤,以客戶端的文本為主。
  // 但是,如果是id不同,則客戶端并不會(huì)糾正。
  return diffHydratedProperties(domInstance, type, props);
}

這里重點(diǎn)講下diffHydratedProperties,以下面的demo為例:

// 服務(wù)端對(duì)應(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的過(guò)程中發(fā)現(xiàn),服務(wù)端返回的id和客戶端的id不同,控制臺(tái)提示id不匹配,但是客戶端并不會(huì)糾正這個(gè),可以看到瀏覽器的id依然是server。

同時(shí),服務(wù)端多返回了一個(gè)extra屬性,因此需要控制臺(tái)提示,但由于已經(jīng)提示了id不同的錯(cuò)誤,這個(gè)錯(cuò)誤就不會(huì)提示。

最后,客戶端的文本和服務(wù)端的children不同,即文本內(nèi)容不同,也需要提示錯(cuò)誤,同時(shí),客戶端會(huì)糾正這個(gè)文本,以客戶端的為主。

prepareToHydrateHostTextInstance

對(duì)于HostText類型的fiber會(huì)調(diào)用這個(gè)方法,這個(gè)方法邏輯比較簡(jiǎn)單,就不詳細(xì)介紹了 務(wù)端對(duì)應(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的過(guò)程中發(fā)現(xiàn),服務(wù)端返回的id和客戶端的id不同,控制臺(tái)提示id不匹配,但是客戶端并不會(huì)糾正這個(gè),可以看到瀏覽器的id依然是server

同時(shí),服務(wù)端多返回了一個(gè)extra屬性,因此需要控制臺(tái)提示,但由于已經(jīng)提示了id不同的錯(cuò)誤,這個(gè)錯(cuò)誤就不會(huì)提示。

最后,客戶端的文本和服務(wù)端的children不同,即文本內(nèi)容不同,也需要提示錯(cuò)誤,同時(shí),客戶端會(huì)糾正這個(gè)文本,以客戶端的為主。

prepareToHydrateHostTextInstance

對(duì)于HostText類型的fiber會(huì)調(diào)用這個(gè)方法,這個(gè)方法邏輯比較簡(jiǎn)單,就不詳細(xì)介紹了

以上就是React Hydrate原理源碼解析的詳細(xì)內(nèi)容,更多關(guān)于React Hydrate原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • react native 文字輪播的實(shí)現(xiàn)示例

    react native 文字輪播的實(shí)現(xiàn)示例

    這篇文章主要介紹了react native 文字輪播的實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • 淺談React-router v6 實(shí)現(xiàn)登錄驗(yàn)證流程

    淺談React-router v6 實(shí)現(xiàn)登錄驗(yàn)證流程

    本文主要介紹了React-router v6 實(shí)現(xiàn)登錄驗(yàn)證流程,主要介紹了公共頁(yè)面、受保護(hù)頁(yè)面和登錄頁(yè)面,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • React進(jìn)階學(xué)習(xí)之組件的解耦之道

    React進(jìn)階學(xué)習(xí)之組件的解耦之道

    這篇文章主要給大家介紹了關(guān)于React進(jìn)階之組件的解耦之道,文中通過(guò)詳細(xì)的示例代碼給大家介紹了組件分割與解耦的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-08-08
  • react拖拽組件react-sortable-hoc的使用

    react拖拽組件react-sortable-hoc的使用

    本文主要介紹了react拖拽組件react-sortable-hoc的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • React 數(shù)據(jù)獲取與性能優(yōu)化詳解

    React 數(shù)據(jù)獲取與性能優(yōu)化詳解

    這篇文章主要為大家介紹了React 數(shù)據(jù)獲取與性能優(yōu)化方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • React使用Context的一些優(yōu)化建議

    React使用Context的一些優(yōu)化建議

    Context?提供了一個(gè)無(wú)需為每層組件手動(dòng)添加?props,就能在組件樹(shù)間進(jìn)行數(shù)據(jù)傳遞的方法,本文為大家整理了React使用Context的一些優(yōu)化建議,希望對(duì)大家有所幫助
    2024-04-04
  • react解析html字符串方法實(shí)現(xiàn)

    react解析html字符串方法實(shí)現(xiàn)

    在使用reactjs庫(kù)的時(shí)候,會(huì)遇到將一段html的字符串,然后要將它插入頁(yè)面中以html的形式展現(xiàn),本文主要介紹了react解析html字符串方法實(shí)現(xiàn),感興趣的可以了解一下
    2023-12-12
  • useEffect中不能使用async原理詳解

    useEffect中不能使用async原理詳解

    這篇文章主要為大家介紹了useEffect中為什么不能使用async的原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • React?Native集成支付寶支付的實(shí)現(xiàn)方法

    React?Native集成支付寶支付的實(shí)現(xiàn)方法

    這篇文章主要介紹了React?Native集成支付寶支付的實(shí)現(xiàn)現(xiàn),ativeModules是JS代碼調(diào)用原生模塊的橋梁。所以,我們只需要在原生工程中集成支付寶和微信支付的sdk,然后使用NativeModules調(diào)用即可,需要的朋友可以參考下
    2022-02-02
  • ahooks正式發(fā)布React?Hooks工具庫(kù)

    ahooks正式發(fā)布React?Hooks工具庫(kù)

    這篇文章主要為大家介紹了ahooks正式發(fā)布值得擁有的React?Hooks工具庫(kù)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07

最新評(píng)論