欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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源碼通過(guò)Mono-repo 管理倉(cāng)庫(kù),我們也是用pnpm提供的workspaces來(lái)管理我們的代碼倉(cāng)庫(kù),打包我們使用rollup進(jìn)行打包。

倉(cāng)庫(kù)地址

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

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

對(duì)比Mount階段

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

之前的章節(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: 對(duì)應(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)相對(duì)于mountStateupdateState

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

beginWork流程

對(duì)于單一節(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都和之前的對(duì)應(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í)候,說(shuō)明是進(jìn)入了update階段。
  • 根據(jù)currentFiberelement的tag 和 type判斷,如果相同才可以復(fù)用。
  • 通過(guò)雙緩存樹(useFiber)去復(fù)用fiberNode。

useFiber

復(fù)用的邏輯本質(zhì)就是調(diào)用了useFiber, 本質(zhì)上,它是通過(guò)雙緩存書指針alternate,它接受已經(jīng)渲染對(duì)應(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;
}

對(duì)于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還沒(méi)有需要?jiǎng)h除的子fiber
    returnFiber.deletions = [childToDelete];
    returnFiber.flags |= ChildDeletion;
  } else {
    deletions.push(childToDelete);
  }
}

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

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

function reconcileSingleTextNode(
  returnFiber: FiberNode,
  currentFiber: FiberNode | null,
  content: string | number
): FiberNode {
  // update
  if (currentFiber !== null) {
    // 類型沒(méi)有變,可以復(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)記。

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

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

這里我們只對(duì)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流程

通過(guò)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

對(duì)于文本節(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過(guò)程中,對(duì)于存在要?jiǎng)h除的子節(jié)點(diǎn),我們會(huì)保存在當(dāng)前父節(jié)點(diǎn)的deletions, 所以在刪除階段,我們需要根據(jù)當(dāng)前節(jié)點(diǎn)的deletions屬性進(jìn)行對(duì)要?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)的話,我們需要對(duì)每一個(gè)子節(jié)點(diǎn)進(jìn)行commitDeletion的操作。

commitDeletion

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

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

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

/**
 * rootHostNode 找到對(duì)應(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中主要是完成我們上面說(shuō)的2點(diǎn)。

  • DFS深度遍歷子節(jié)點(diǎn)
  • 找到當(dāng)前要?jiǎng)h除的fiberNode對(duì)應(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;
  }
}

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

總結(jié)

如果按照如下的結(jié)構(gòu),要?jiǎng)h除外層div元素,會(huì)經(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對(duì)應(yīng)的HostComponent, 然后執(zhí)行commitDeletion
  • commitDeletion中執(zhí)行commitNestedComponent向下DFS遍歷。
  • 在遍歷的過(guò)程中,每一個(gè)節(jié)點(diǎn)都是執(zhí)行一個(gè)回調(diào)函數(shù),基于不同的類型執(zhí)行不同的刪除操作,以及記錄我們要?jiǎng)h除的Dom節(jié)點(diǎn)對(duì)應(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é)我們講解通過(guò)useState改變狀態(tài)后,如何更新節(jié)點(diǎn)以及函數(shù)組件hooks是如何保存數(shù)據(jù)的。

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

相關(guān)文章

  • 詳解React Native 屏幕適配(炒雞簡(jiǎn)單的方法)

    詳解React Native 屏幕適配(炒雞簡(jiǎn)單的方法)

    React Native 可以開發(fā) ios 和 android 的 app,在開發(fā)過(guò)程中,勢(shì)必會(huì)遇上屏幕適配,這篇文章主要介紹了詳解React Native 屏幕適配(炒雞簡(jiǎn)單的方法),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-06-06
  • 詳解react-redux插件入門

    詳解react-redux插件入門

    這篇文章主要介紹了詳解react-redux插件入門,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • react之umi配置國(guó)際化語(yǔ)言locale的踩坑記錄

    react之umi配置國(guó)際化語(yǔ)言locale的踩坑記錄

    這篇文章主要介紹了react之umi配置國(guó)際化語(yǔ)言locale的踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 淺談React + Webpack 構(gòu)建打包優(yōu)化

    淺談React + Webpack 構(gòu)建打包優(yōu)化

    本篇文章主要介紹了淺談React + Webpack 構(gòu)建打包優(yōu)化,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • React?Native系列之Recyclerlistview使用詳解

    React?Native系列之Recyclerlistview使用詳解

    這篇文章主要為大家介紹了React?Native系列之Recyclerlistview使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • react項(xiàng)目中redux的調(diào)試工具不起作用的解決

    react項(xiàng)目中redux的調(diào)試工具不起作用的解決

    這篇文章主要介紹了react項(xiàng)目中redux的調(diào)試工具不起作用的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng)

    react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng)

    這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 如何在react項(xiàng)目中做公共配置文件

    如何在react項(xiàng)目中做公共配置文件

    這篇文章主要介紹了如何在react項(xiàng)目中做公共配置文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • React Grid Layout基礎(chǔ)使用示例教程

    React Grid Layout基礎(chǔ)使用示例教程

    React Grid Layout是一個(gè)用于在React應(yīng)用程序中創(chuàng)建可拖拽和可調(diào)整大小的網(wǎng)格布局的庫(kù),通過(guò)使用React Grid Layout,我們可以輕松地創(chuàng)建自適應(yīng)的網(wǎng)格布局,并實(shí)現(xiàn)拖拽和調(diào)整大小的功能,本文介紹了React Grid Layout的基礎(chǔ)使用方法,感興趣的朋友一起看看吧
    2024-02-02
  • 原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例

    原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例

    這篇文章主要介紹了原生實(shí)現(xiàn)一個(gè)react-redux的代碼示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-06-06

最新評(píng)論