最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React18之update流程從零實(shí)現(xiàn)詳解

 更新時(shí)間:2023年01月10日 09:49:22   作者:sunnyhuang519626  
這篇文章主要為大家介紹了React18之update流程從零實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

本系列是講述從0開始實(shí)現(xiàn)一個(gè)react18的基本版本。由于React源碼通過Mono-repo 管理倉庫,我們也是用pnpm提供的workspaces來管理我們的代碼倉庫,打包我們使用rollup進(jìn)行打包。

倉庫地址

具體章節(jié)代碼3個(gè)commit

本章我們主要講解通過useState狀態(tài)改變,引起的單節(jié)點(diǎn)update更新階段的流程。

對比Mount階段

對比我們之前講解的mount階段,update階段也會經(jīng)歷大致的流程, 只是處理邏輯會有不同:

之前的章節(jié)我們主要講了reconciler(調(diào)和) 階段中mount階段:

  • beginWork:向下調(diào)和創(chuàng)建fiberNode樹,
  • completeWork:構(gòu)建離屏DOM樹以及打subtreeFlags標(biāo)記。
  • commitWork:根據(jù)placement創(chuàng)建dom
  • useState: 對應(yīng)調(diào)用mountState

這一節(jié)的update階段如下:

begionWork階段:

  • 處理ChildDeletion的刪除的情況
  • 處理節(jié)點(diǎn)移動(dòng)的情況 (abc -> bca)

completeWork階段:

  • 基于HostText的內(nèi)容更新標(biāo)記更新flags
  • 基于HostComponent屬性變化標(biāo)記更新flags

commitWork階段:

  • 基于ChildDeletion, 遍歷被刪除的子樹
  • 基于Update, 更新文本內(nèi)容

useState階段:

  • 實(shí)現(xiàn)相對于mountStateupdateState

下面我們分別一一地實(shí)現(xiàn)單節(jié)點(diǎn)的update更新流程

beginWork流程

對于單一節(jié)點(diǎn)的向下調(diào)和流程,主要在childFibers文件中,分2種,一種是文本節(jié)點(diǎn)的處理reconcileSingleTextNode, 一種是標(biāo)簽節(jié)點(diǎn)的處理reconcileSingleElement。

復(fù)用fiberNode

update階段的話,主要有一點(diǎn)是要思考如何復(fù)用之前mount階段已經(jīng)創(chuàng)建的fiberNode。

我們先以reconcileSingleElement為例子講解。

當(dāng)新的ReactElement的type 和 key都和之前的對應(yīng)的fiberNode都一樣的時(shí)候,才能夠進(jìn)行復(fù)用。我們先看看reconcileSingleElement是復(fù)用的邏輯。

function reconcileSingleElement(
  returnFiber: FiberNode,
  currentFiber: FiberNode | null,
  element: ReactElementType
) {
  const key = element.key;
  // update的情況 <單節(jié)點(diǎn)的處理 div -> p>
  if (currentFiber !== null) {
    // key相同
    if (currentFiber.key === key) {
      // 是react元素
      if (element.$$typeof === REACT_ELEMENT_TYPE) {
        // type相同
        if (currentFiber.type === element.type) {
          const existing = useFiber(currentFiber, element.props);
          existing.return = returnFiber;
          return existing;
        }
      }
    }
  }
}
  • 首先我們需要判斷currentFiber是否存在,當(dāng)存在的時(shí)候,說明是進(jìn)入了update階段。
  • 根據(jù)currentFiberelement的tag 和 type判斷,如果相同才可以復(fù)用。
  • 通過雙緩存樹(useFiber)去復(fù)用fiberNode。

useFiber

復(fù)用的邏輯本質(zhì)就是調(diào)用了useFiber, 本質(zhì)上,它是通過雙緩存書指針alternate,它接受已經(jīng)渲染對應(yīng)的fiberNode以及新的Props 巧妙的運(yùn)用我們之前創(chuàng)建wip的邏輯,可以很好的復(fù)用fiberNode。

/**
 * 雙緩存樹原理:基于當(dāng)前的fiberNode創(chuàng)建一個(gè)新的fiberNode, 而不用去調(diào)用new FiberNode
 * @param {FiberNode} fiber 正在展示的fiberNode
 * @param {Props} pendingProps 新的Props
 * @returns {FiberNode}
 */
function useFiber(fiber: FiberNode, pendingProps: Props): FiberNode {
  const clone = createWorkInProgress(fiber, pendingProps);
  clone.index = 0;
  clone.sibling = null;
  return clone;
}

對于reconcileSingleTextNode

刪除舊的和新建fiberNode

當(dāng)不能夠復(fù)用fiberNode的時(shí)候,我們除了要像mount的時(shí)候新建fiberNode(已經(jīng)有的邏輯),還需要?jiǎng)h除舊的fiberNode

我們先以reconcileSingleElement為例子講解。

beginWork階段,我們只需要標(biāo)記刪除flags。以下2種情況我們需要額外的標(biāo)記舊fiberNode刪除

  • key不同
  • key相同,type不同
function deleteChild(returnFiber: FiberNode, childToDelete: FiberNode) {
  if (!shouldTrackEffects) {
    return;
  }
  const deletions = returnFiber.deletions;
  if (deletions === null) {
    // 當(dāng)前父fiber還沒有需要?jiǎng)h除的子fiber
    returnFiber.deletions = [childToDelete];
    returnFiber.flags |= ChildDeletion;
  } else {
    deletions.push(childToDelete);
  }
}

我們將需要?jiǎng)h除的節(jié)點(diǎn),通過數(shù)組形式賦值到父節(jié)點(diǎn)deletions中,并標(biāo)記ChildDeletion有節(jié)點(diǎn)需要?jiǎng)h除。

對于reconcileSingleTextNode, 當(dāng)渲染視圖中是HostText就可以直接復(fù)用。整體代碼如下:

function reconcileSingleTextNode(
  returnFiber: FiberNode,
  currentFiber: FiberNode | null,
  content: string | number
): FiberNode {
  // update
  if (currentFiber !== null) {
    // 類型沒有變,可以復(fù)用
    if (currentFiber.tag === HostText) {
      const existing = useFiber(currentFiber, { content });
      existing.return = returnFiber;
      return existing;
    }
    // 刪掉之前的 (之前的div, 現(xiàn)在是hostText)
    deleteChild(returnFiber, currentFiber);
  }
  const fiber = new FiberNode(HostText, { content }, null);
  fiber.return = returnFiber;
  return fiber;
}

completeWork流程

當(dāng)在beginWork做好相應(yīng)的刪除和移動(dòng)標(biāo)記后,在completeWork主要是做更新的標(biāo)記。

對于單一的節(jié)點(diǎn)來說,更新標(biāo)記分為2種,

  • 第一種是文本元素的更新,主要是新舊文本內(nèi)容的不一樣。
  • 第二種是類似div的屬性等更新。這個(gè)我們下一節(jié)進(jìn)行講解。

這里我們只對HostText中的類型進(jìn)行講解。

case HostText:
  if (current !== null && wip.stateNode) {
    //update
    const oldText = current.memoizedProps.content;
    const newText = newProps.content;
    if (oldText !== newText) {
      // 標(biāo)記更新
      markUpdate(wip);
    }
  } else {
    // 1. 構(gòu)建DOM
    const instance = createTextInstance(newProps.content);
    // 2. 將DOM插入到DOM樹中
    wip.stateNode = instance;
  }
  bubbleProperties(wip);
  return null;

從上面我們可以看出,我們根據(jù)文本內(nèi)容的不同,進(jìn)行當(dāng)前節(jié)點(diǎn)wip進(jìn)行標(biāo)記。

function markUpdate(fiber: FiberNode) {
  fiber.flags |= Update;
}

commitWork流程

通過beginWorkcompleteWork之后,我們得到了相應(yīng)的標(biāo)記。在commitWork階段,我們就需要根據(jù)相應(yīng)標(biāo)記去處理不同的邏輯。本節(jié)主要講解更新刪除階段的處理。

更新update

在之前的章節(jié)中,我們講解了commitWorkmount階段,我們現(xiàn)在根據(jù)update的flag進(jìn)行邏輯處理。

// flags update
if ((flags & Update) !== NoFlags) {
  commitUpdate(finishedWork);
  finishedWork.flags &= ~Update;
}

commitUpdate

對于文本節(jié)點(diǎn),commitUpdate主要是根據(jù)新的文本內(nèi)容,更新之前的dom的文本內(nèi)容。

export function commitUpdate(fiber: FiberNode) {
  switch (fiber.tag) {
    case HostText:
      const text = fiber.memoizedProps.content;
      return commitTextUpdate(fiber.stateNode, text);
  }
}
export function commitTextUpdate(textInstance: TestInstance, content: string) {
  textInstance.textContent = content;
}

刪除ChildDeletion

beginWork過程中,對于存在要?jiǎng)h除的子節(jié)點(diǎn),我們會保存在當(dāng)前父節(jié)點(diǎn)的deletions, 所以在刪除階段,我們需要根據(jù)當(dāng)前節(jié)點(diǎn)的deletions屬性進(jìn)行對要?jiǎng)h除的節(jié)點(diǎn)進(jìn)行不同的處理。

// flags childDeletion
if ((flags & ChildDeletion) !== NoFlags) {
  const deletions = finishedWork.deletions;
  if (deletions !== null) {
    deletions.forEach((childToDelete) => {
      commitDeletion(childToDelete);
    });
  }
  finishedWork.flags &= ~ChildDeletion;
}

如果當(dāng)前節(jié)點(diǎn)存在要?jiǎng)h除的子節(jié)點(diǎn)的話,我們需要對每一個(gè)子節(jié)點(diǎn)進(jìn)行commitDeletion的操作。

commitDeletion

commitDeletion函數(shù)的是對每一個(gè)要?jiǎng)h除的子節(jié)點(diǎn)進(jìn)行處理。它的主要功能有幾點(diǎn):

  • 對于不同類型的fiberNode, 當(dāng)節(jié)點(diǎn)刪除的時(shí)候,自身和所有子節(jié)點(diǎn)都需要執(zhí)行的不同的卸載邏輯。例如:函數(shù)組件的useEffect的return函數(shù)執(zhí)行,ref的解綁,class組件的componentUnmount等邏輯處理。
  • 由于fiberNode和dom節(jié)點(diǎn)不是一一對應(yīng)的,所以要找到fiberNode對應(yīng)的dom節(jié)點(diǎn),然后再執(zhí)行刪除dom節(jié)點(diǎn)的操作。
  • 最后將刪除的節(jié)點(diǎn)的childreturn指向刪掉。

基于上面的2點(diǎn)分析,我們很容易就想到,commitDeletion肯定會執(zhí)行DFS向下遍歷,進(jìn)行不同子節(jié)點(diǎn)的刪除邏輯處理。

/**
 * rootHostNode 找到對應(yīng)的DOM節(jié)點(diǎn)。
 * commitNestedComponent DFS遍歷節(jié)點(diǎn)的進(jìn)行卸載相關(guān)的邏輯
 * @param {FiberNode} childToDelete
 */
function commitDeletion(childToDelete: FiberNode) {
  let rootHostNode: FiberNode | null = null;
  // 遞歸子樹
  commitNestedComponent(childToDelete, (unmountFiber) => {
    switch (unmountFiber.tag) {
      case HostComponent:
        if (rootHostNode === null) {
          rootHostNode = unmountFiber;
        }
        // TODO: 解綁ref
        return;
      case HostText:
        if (rootHostNode === null) {
          rootHostNode = unmountFiber;
        }
        return;
      case FunctionComponent:
        // TODO: useEffect unmount 解綁ref
        return;
      default:
        if (__DEV__) {
          console.warn("未處理的unmount類型", unmountFiber);
        }
        break;
    }
  });
  // 移除rootHostNode的DOM
  if (rootHostNode !== null) {
    const hostParent = getHostParent(childToDelete);
    if (hostParent !== null) {
      removeChild((rootHostNode as FiberNode).stateNode, hostParent);
    }
  }
  childToDelete.return = null;
  childToDelete.child = null;
}

commitNestedComponent

commitNestedComponent中主要是完成我們上面說的2點(diǎn)。

  • DFS深度遍歷子節(jié)點(diǎn)
  • 找到當(dāng)前要?jiǎng)h除的fiberNode對應(yīng)的真正的DOM節(jié)點(diǎn)

接受2個(gè)參數(shù)。1. 當(dāng)前的fiberNode, 2. 遞歸到不同的子節(jié)點(diǎn)的同時(shí),需要執(zhí)行的回調(diào)函數(shù)執(zhí)行不同的卸載流程。

function commitNestedComponent(
  root: FiberNode,
  onCommitUnmount: (fiber: FiberNode) => void
) {
  let node = root;
  while (true) {
    onCommitUnmount(node);
    if (node.child !== null) {
      // 向下遍歷
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === root) {
      // 終止條件
      return;
    }
    while (node.sibling === null) {
      if (node.return === null || node.return === root) {
        return;
      }
      // 向上歸
      node = node.return;
    }
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

這里可能比較繞,我們下面通過幾個(gè)例子總結(jié)一下,這個(gè)過程的主要流程。

總結(jié)

如果按照如下的結(jié)構(gòu),要?jiǎng)h除外層div元素,會經(jīng)歷如下的流程

<div>
   <Child />
   <span>hcc</span>
   yx
</div>
function Child() {
  return <div>hello world</div>
}
  • div的fiberNode的父節(jié)的標(biāo)記ChildDeletion以及存放到deletions中。
  • 當(dāng)執(zhí)行到commitWork階段的時(shí)候,遍歷deletions數(shù)組。
  • 執(zhí)行的div對應(yīng)的HostComponent, 然后執(zhí)行commitDeletion
  • commitDeletion中執(zhí)行commitNestedComponent向下DFS遍歷。
  • 在遍歷的過程中,每一個(gè)節(jié)點(diǎn)都是執(zhí)行一個(gè)回調(diào)函數(shù),基于不同的類型執(zhí)行不同的刪除操作,以及記錄我們要?jiǎng)h除的Dom節(jié)點(diǎn)對應(yīng)的fiberNode。
  • 所以首先是div執(zhí)行onCommitUnmount, 由于它是HostComponent,所以將rootHostNode賦值給了div
  • 向下遞歸到Child節(jié)點(diǎn),由于它存在子節(jié)點(diǎn),繼續(xù)遞歸到child-div節(jié)點(diǎn),繼續(xù)遍歷到hello world節(jié)點(diǎn)。它不存在子節(jié)點(diǎn)。
  • 然后找到Child的兄弟節(jié)點(diǎn),以此執(zhí)行,先子后兄。直到回到div節(jié)點(diǎn)。

下一節(jié)預(yù)告

下一節(jié)我們講解通過useState改變狀態(tài)后,如何更新節(jié)點(diǎn)以及函數(shù)組件hooks是如何保存數(shù)據(jù)的。

以上就是React18之update流程從零實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于React18 update流程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談React中組件間抽象

    淺談React中組件間抽象

    這篇文章主要介紹了淺談React中組件間抽象,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • 淺談React 屬性和狀態(tài)的一些總結(jié)

    淺談React 屬性和狀態(tài)的一些總結(jié)

    下面小編就為大家?guī)硪黄獪\談React 屬性和狀態(tài)的一些總結(jié)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-11-11
  • 詳解如何在React中執(zhí)行條件渲染

    詳解如何在React中執(zhí)行條件渲染

    在現(xiàn)代Web開發(fā)中,React作為一種流行的JavaScript庫,為開發(fā)者提供了一種高效構(gòu)建用戶界面的方式,條件渲染是React中的一個(gè)關(guān)鍵概念,本文將深入探討如何在React中實(shí)現(xiàn)條件渲染,并通過示例代碼演示常見的用法,需要的朋友可以參考下
    2025-01-01
  • React18的useEffect執(zhí)行兩次如何應(yīng)對

    React18的useEffect執(zhí)行兩次如何應(yīng)對

    這篇文章主要給大家介紹了關(guān)于React18的useEffect執(zhí)行兩次如何應(yīng)對的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用React具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • React+CSS?實(shí)現(xiàn)繪制豎狀柱狀圖

    React+CSS?實(shí)現(xiàn)繪制豎狀柱狀圖

    這篇文章主要介紹了React+CSS?實(shí)現(xiàn)繪制豎狀柱狀圖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹。具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-09-09
  • 無廢話快速上手React路由開發(fā)

    無廢話快速上手React路由開發(fā)

    本文以簡潔為目標(biāo),幫助快速上手react-router-dom默認(rèn)你接觸過路由相關(guān)的開發(fā),通過實(shí)例代碼講解的很詳細(xì),對React路由相關(guān)知識感興趣的朋友一起看看吧
    2021-05-05
  • ReactNative頁面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼

    ReactNative頁面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼

    本篇文章主要介紹了ReactNative頁面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08
  • React中setState同步異步場景的使用

    React中setState同步異步場景的使用

    本文主要介紹了React中setState同步異步場景的使用,文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • create-react-app使用antd按需加載的樣式無效問題的解決

    create-react-app使用antd按需加載的樣式無效問題的解決

    這篇文章主要介紹了create-react-app使用antd按需加載的樣式無效問題的解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-02-02
  • react實(shí)現(xiàn)同頁面三級跳轉(zhuǎn)路由布局

    react實(shí)現(xiàn)同頁面三級跳轉(zhuǎn)路由布局

    這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)同頁面三級跳轉(zhuǎn)路由布局,一個(gè)路由小案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09

最新評論

云安县| 南部县| 高州市| 平顶山市| 额敏县| 久治县| 徐州市| 和林格尔县| 奉节县| 合作市| 华容县| 安图县| 沾化县| 宾阳县| 凯里市| 湖南省| 惠安县| 荔波县| 荣成市| 郓城县| 咸宁市| 汉川市| 锦州市| 湖南省| 额尔古纳市| 白沙| 崇文区| 浦城县| 菏泽市| 拜泉县| 沧源| 尼木县| 浪卡子县| 斗六市| 巢湖市| 教育| 普陀区| 毕节市| 建始县| 榕江县| 茌平县|