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

React18從0實(shí)現(xiàn)dispatch?update流程

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

引言

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

倉庫地址

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

上一節(jié)中我們講解了update的過程中,begionWorkcompleteWork、commitWork的具體執(zhí)行流程。本節(jié)主要是講解

  • hooks是如何存放數(shù)據(jù)的,以及一些hooks的規(guī)則。
  • 一次dispatch觸發(fā)的更新整體流程,雙緩存樹的運(yùn)用。

我們有如下代碼,在初始化的時(shí)候執(zhí)行useState和調(diào)用setNum的時(shí)候,是如何更新的。

function App() {
  const [num, setNum] = useState(100);
  window.setNum = setNum;
  return <div>{num}</div>;
}

hooks原理

基于useState我們來講講hook在初始化和更新階段的區(qū)別。以及react是如何做到hook不能在條件語句和函數(shù)組件外部使用的。

react中,對(duì)于同一個(gè)hook,在不同的環(huán)境都是有不同的集合區(qū)分,這樣就可以做到基于不同的執(zhí)行環(huán)境的不同判斷。

首先有幾個(gè)名詞:

currentlyRenderingFiber: 記錄當(dāng)前正在執(zhí)行的函數(shù)組件的fiberNode

workInProgressHook: 當(dāng)前正在執(zhí)行的hook

currentHook:更新的時(shí)候的數(shù)據(jù)來源

memoizedState: 對(duì)于fiberNode.memoizedState是存放hooks的指向。對(duì)于hook.memoizedState就是存放數(shù)據(jù)的地方。

hook的結(jié)構(gòu)如下圖:

useState初始化(mount)

我們知道當(dāng)beginWork階段的時(shí)候,對(duì)于函數(shù)組件,會(huì)執(zhí)行renderWithHooks去生成當(dāng)前對(duì)應(yīng)的子fiberNode。 我們首先來看看renderWithHooks的邏輯部分。

export function renderWithHooks(wip: FiberNode) {
  // 賦值操作
  currentlyRenderingFiber = wip;
  // 重置
  wip.memoizedState = null;
  const current = wip.alternate;
  if (current !== null) {
    // update
    currentDispatcher.current = HooksDispatcherOnUpdate;
  } else {
    // mount
    currentDispatcher.current = HooksDispatcherOnMount;
  }
  const Component = wip.type;
  const props = wip.pendingProps;
  const children = Component(props);
  // 重置操作
  currentlyRenderingFiber = null;
  workInProgressHook = null;
  currentHook = null;
  return children;
}

首先會(huì)將currentlyRenderingFiber賦值給當(dāng)前的FC的fiberNode,然后重置掉memoizedState, 因?yàn)槌跏蓟臅r(shí)候會(huì)生成,更新的時(shí)候會(huì)根據(jù)初始化的時(shí)候生成。

可以看到對(duì)于mount階段,主要是執(zhí)行HooksDispatcherOnMount, 他實(shí)際上是一個(gè)hook集合。我們主要看看mountState的邏輯處理。

const HooksDispatcherOnMount: Dispatcher = {
  useState: mountState,
};

mountState

對(duì)于第一次執(zhí)行useState, 我們根據(jù)結(jié)果來推算這個(gè)函數(shù)的主要功能。useState需要返回2個(gè)值,第一個(gè)是state,第二個(gè)是可以引發(fā)更新的setState。所以mountState的主要功能:

  • 根據(jù)傳入的initialState生成新的state
  • 返回dispatch,便于之后調(diào)用更新state

基于hook的結(jié)構(gòu)圖,我們知道每一個(gè)hook有三個(gè)屬性, 所以我們首先要有一個(gè)函數(shù)去生成對(duì)應(yīng)的hook的結(jié)構(gòu)。

interface Hook {
  memoizedState: any;
  updateQueue: unknown;
  next: Hook | null;
}

mountWorkInProgressHook

mountWorkInProgressHook這個(gè)函數(shù)主要是構(gòu)建hook的數(shù)據(jù)。分為2種情況,第一種是第一個(gè)hook, 第二種是不是第一個(gè)hook就需要通過next屬性,將hook串聯(lián)起來。

在這個(gè)函數(shù)中,我們就可以判斷當(dāng)前執(zhí)行的hook,是否是在函數(shù)中執(zhí)行的。如果是在函數(shù)中執(zhí)行的話,在執(zhí)行函數(shù)組件的時(shí)候,我們將currentlyRenderingFiber 賦值給了wip, 如果是直接調(diào)用的話,currentlyRenderingFiber則為null,我們就可以拋出錯(cuò)誤。

/**
 * mount獲取當(dāng)前hook對(duì)應(yīng)的數(shù)據(jù)
 */
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,
    updateQueue: null,
    next: null,
  };
  if (workInProgressHook === null) {
    // mount時(shí),第一個(gè)hook
    if (currentlyRenderingFiber === null) {
      throw new Error("請(qǐng)?jiān)诤瘮?shù)組件內(nèi)調(diào)用hook");
    } else {
      workInProgressHook = hook;
      currentlyRenderingFiber.memoizedState = workInProgressHook;
    }
  } else {
    // mount時(shí),后續(xù)的hook
    workInProgressHook.next = hook;
    workInProgressHook = hook;
  }
  return workInProgressHook;
}

當(dāng)?shù)谝淮螆?zhí)行的時(shí)候,workInProgressHook的值為null, 說明是第一個(gè)hook執(zhí)行。所以我們將賦值workInProgressHook正在執(zhí)行的hook, 同時(shí)將FC fiberNodememoizedState指向第一個(gè)hook。此時(shí)就生成了如下圖的結(jié)構(gòu):

處理hook數(shù)據(jù)

通過mountWorkInProgressHook我們得到當(dāng)前的hook結(jié)構(gòu)后,需要處理memoizedState以及updateQueue的值。

function mountState<State>(
  initialState: (() => State) | State
): [State, Dispatch<State>] {
  // 找到當(dāng)前useState對(duì)應(yīng)的hook數(shù)據(jù)
  const hook = mountWorkInProgressHook();
  let memoizedState;
  if (initialState instanceof Function) {
    memoizedState = initialState();
  } else {
    memoizedState = initialState;
  }
  // useState是可以觸發(fā)更新的
  const queue = createUpdateQueue<State>();
  hook.updateQueue = queue;
  hook.memoizedState = memoizedState;
  //@ts-ignore
  const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
  queue.dispatch = dispatch;
  return [memoizedState, dispatch];
}

從上面的代碼中,我們可以看出memoizedState的處理很簡(jiǎn)單,就是通過傳入的參數(shù),進(jìn)行賦值處理,重點(diǎn)在于如何生成dispatch

生成dispatch

因?yàn)橛|發(fā)dispatch的時(shí)候,react是要觸發(fā)更新的,所以必然會(huì)和調(diào)度有關(guān)。

由于要觸發(fā)更新,我們就需要?jiǎng)?chuàng)建觸發(fā)更新的隊(duì)列

  • 執(zhí)行createUpdateQueue() 生成更新隊(duì)列。
  • 將更新隊(duì)列賦值給當(dāng)前hook保存起來,方便之后update使用。
  • 將生成的dispatch保存起來,方便之后update使用。
// useState是可以觸發(fā)更新的
const queue = createUpdateQueue<State>();
hook.updateQueue = queue;
const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;

主要是看如何生成dispatch的邏輯,通過調(diào)用dispatchSetState它接受三個(gè)參數(shù),因?yàn)槲覀冃枰朗菑哪囊粋€(gè)fiberNode開始調(diào)度的,所以當(dāng)前的fiberNode是肯定看需要的。更新隊(duì)列queue也是需要的,用于執(zhí)行dispatch的時(shí)候觸發(fā)更新。

function dispatchSetState<State>(
  fiber: FiberNode,
  updateQueue: UpdateQueue<State>,
  action: Action<State>
) {
  const update = createUpdate(action); // 1. 創(chuàng)建update
  enqueueUpdate(updateQueue, update); //  2. 將更新放入隊(duì)列中
  scheduleUpdateOnFiber(fiber); // 3. 開始調(diào)度
}

所以我們每次執(zhí)行setState的時(shí)候,等同于執(zhí)行上面函數(shù),但是我們只需要傳遞action就可以,前2個(gè)參數(shù),已經(jīng)通過bind綁定。

執(zhí)行dispatch后,開始新一輪的調(diào)度,調(diào)和。

更新的總結(jié)

從上面的代碼,我們可以看出我們首先是執(zhí)行了createUpdateQueue, 然后執(zhí)行了createUpdate, 然后enqueueUpdate。這里總結(jié)一下這些函數(shù)調(diào)用。

createUpdateQueue本質(zhì)上就創(chuàng)建了一個(gè)對(duì)象,用于保存值

 return {
   shared: {
     pending: null,
   },
   dispatch: null,
 }

createUpdate就是也是返回一個(gè)對(duì)象。

return {
  action,
};

enqueueUpdate就是將createUpdateQueue的pending 賦值。

{
  updateQueue.shared.pending = update;
};

最后我們生成的單個(gè)hook結(jié)構(gòu)如下圖:

useState觸發(fā)更新(dispatch)

當(dāng)我們執(zhí)行setNum(3)的時(shí)候,我們之前講過相當(dāng)于是執(zhí)行了下面函數(shù), 將傳遞3為action的值。

function dispatchSetState<State>(
  fiber: FiberNode,
  updateQueue: UpdateQueue<State>,
  action: Action<State>
) {
  const update = createUpdate(action);
  enqueueUpdate(updateQueue, update); 
  scheduleUpdateOnFiber(fiber); // 3. 開始調(diào)度
}

當(dāng)再次執(zhí)行到函數(shù)組件App的時(shí)候,會(huì)執(zhí)行renderWithHooks如下的邏輯。將 currentDispatcher.current賦值給HooksDispatcherOnUpdate

// 賦值操作
currentlyRenderingFiber = wip;
// 重置
wip.memoizedState = null;
const current = wip.alternate;
if (current !== null) {
  // update
  currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
  // mount
  currentDispatcher.current = HooksDispatcherOnMount;
}

然后執(zhí)行App函數(shù),重新會(huì)調(diào)用useState

const [num, setNum] = useState(100);

updateState

HooksDispatcherOnUpdate中,useState對(duì)應(yīng)的是updateState。對(duì)比于mountState的話,updateState主要是:

  • hook的數(shù)據(jù)從哪里來
  • 會(huì)有2種情況執(zhí)行,交互階段觸發(fā),render的時(shí)候觸發(fā)

本節(jié)主要是分析交互階段的觸發(fā)的邏輯。

hook數(shù)據(jù)從哪里來

對(duì)比mountState中,我們可以通過新建hook數(shù)據(jù)結(jié)構(gòu)。這個(gè)時(shí)候雙緩存樹的結(jié)構(gòu)就可以解決,還記得我們之前的章節(jié)講的react將正在渲染的和正在進(jìn)行的分2個(gè)樹,通過alternate進(jìn)行鏈接。整體結(jié)構(gòu)如下圖:

還記得我們mount的時(shí)候說過,fiberNode.memoizedState的指向保存著hook的數(shù)據(jù)。

所以我們可以通過currentlyRenderingFiber?.alternate中的memoizedState去查找對(duì)應(yīng)的hook數(shù)據(jù)。

updateWorkInProgressHook

更新階段hook的數(shù)據(jù)獲取是通過updateWorkInProgressHook執(zhí)行的。

function updateWorkInProgressHook(): Hook {
  // TODO render階段觸發(fā)的更新
  let nextCurrentHook: Hook | null;
  // FC update時(shí)的第一個(gè)hook
  if (currentHook === null) {
    const current = currentlyRenderingFiber?.alternate;
    if (current !== null) {
      nextCurrentHook = current?.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    // FC update時(shí)候,后續(xù)的hook
    nextCurrentHook = currentHook.next;
  }
  if (nextCurrentHook === null) {
    // mount / update u1 u2 u3 
    // update u1 u2 u3 u4
    throw new Error(
      `組件${currentlyRenderingFiber?.type}本次執(zhí)行時(shí)的Hook比上次執(zhí)行的多`
    );
  }
  currentHook = nextCurrentHook as Hook;
  const newHook: Hook = {
    memoizedState: currentHook.memoizedState,
    updateQueue: currentHook.updateQueue,
    next: null,
  };
  if (workInProgressHook === null) {
    // update時(shí),第一個(gè)hook
    if (currentlyRenderingFiber === null) {
      throw new Error("請(qǐng)?jiān)诤瘮?shù)組件內(nèi)調(diào)用hook");
    } else {
      workInProgressHook = newHook;
      currentlyRenderingFiber.memoizedState = workInProgressHook;
    }
  } else {
    // update時(shí),后續(xù)的hook
    workInProgressHook.next = newHook;
    workInProgressHook = newHook;
  }
  return workInProgressHook;
}

主要邏輯總結(jié)如下:

  • 剛開始currentHook為null, 通過alternate指向memoizedState獲取到正在渲染中的hook數(shù)據(jù),賦值給nextCurrentHook
  • currentHook賦值為nextCurrentHook, 記錄更新的數(shù)據(jù)來源,方便之后的hook,通過next連接起來。
  • 賦值workInProgressHook標(biāo)記正在執(zhí)行的hook

這里有一個(gè)難點(diǎn),就是nextCurrentHook === null的時(shí)候,我們可以拋出錯(cuò)誤。

hook在條件語句中報(bào)錯(cuò)

我們曉得hook是不能在條件語句中執(zhí)行的。那是如何做到報(bào)錯(cuò)的呢?接下來我們根據(jù)上面的updateWorkProgressHook源碼分析。假如,偽代碼如下所示: 在mount階段的時(shí)候,是3個(gè)hook,在執(zhí)行setNum(100),update階段4個(gè)。

const [num, setNum] = useState(99);
const [num2, setNum] = useState(101);
const [num3, setNum] = useState(102);
if(num === 100) {
 const [num4, setNum] = useState(103);
}

這里我們就會(huì)執(zhí)行四次updateWorkProgressHook,我們來分析一下。

  • nextCurrentHook = currentHook = m-hook1,第一次后currentHook不為null
  • nextCurrentHook等于m-hook2
  • nextCurrentHook等于m-hook3
  • 第四次的時(shí)候nextCurrentHook = m-hook3.next = null, 所以就會(huì)走到報(bào)錯(cuò)的邏輯。

useState計(jì)算

上一部分我們已經(jīng)知道了update的時(shí)候,hook的數(shù)據(jù)來源,我們現(xiàn)在得到數(shù)據(jù)了,那如何通過之前的數(shù)據(jù),計(jì)算出新的數(shù)據(jù)呢?

  • 在執(zhí)行setNum(action)后,我們知道action 存放在queue.shared.pending
  • queue是存放在對(duì)應(yīng)hookupdateQueue中。所以我們可以拿到action
  • 第三步就是去消費(fèi)action,即執(zhí)行processUpdateQueue, 傳入上一次的state, 以及我們這次接受的action,計(jì)算最新的值。
function updateState<State>(): [State, Dispatch<State>] {
  // 找到當(dāng)前useState對(duì)應(yīng)的hook數(shù)據(jù)
  const hook = updateWorkInProgressHook();
  // 計(jì)算新的state邏輯
  const queue = hook.updateQueue as UpdateQueue<State>;
  const pending = queue.shared.pending;
  if (pending !== null) {
    const { memoizedState } = processUpdateQueue(hook.memoizedState, pending);
    hook.memoizedState = memoizedState;
  }
  return [hook.memoizedState, queue.dispatch as Dispatch<State>];
}

這樣,我們就在渲染的時(shí)候拿到了最新的值,以及重新返回的dispatch

雙緩存樹

在第一次更新的時(shí)候,我們的雙緩存樹還沒有建立起來,在第一次更新之后,雙緩存樹就建立完成。

之后每一次調(diào)和生成子fiberNode的時(shí)候,都會(huì)利用alternate指針去重復(fù)利用相同type和相同key的節(jié)點(diǎn)。

例如初始化的時(shí)候num的值為3, 通過setNum(4)調(diào)用第一次更新后。首先會(huì)創(chuàng)建一個(gè)wip tree

在執(zhí)行完commitWork后,屏幕上渲染為4后,root.current的指向會(huì)被修改 為wip tree。

當(dāng)我們?cè)?code>setNum(5)的時(shí)候,第二次更新后,雙緩存樹已經(jīng)建立。會(huì)利用之前右邊的4fiberNode tree,進(jìn)行下一輪渲染。

總結(jié)

此節(jié)我們主要是講了hook是如何存放數(shù)據(jù)的,以及mount階段和update階段不同的存放,也講解了通過dispatch調(diào)用后,react是如何更新的。以及雙緩存樹在第一次更新后是如何建立的。

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

相關(guān)文章

  • react實(shí)現(xiàn)列表滾動(dòng)組件功能

    react實(shí)現(xiàn)列表滾動(dòng)組件功能

    在開發(fā)項(xiàng)目的時(shí)候,從服務(wù)端獲取到數(shù)據(jù)列表后,展示給用戶看,需要實(shí)現(xiàn)數(shù)據(jù)自動(dòng)滾動(dòng)效果,怎么實(shí)現(xiàn)呢,下面小編給大家分享react實(shí)現(xiàn)列表滾動(dòng)組件功能實(shí)現(xiàn)代碼,感興趣的朋友一起看看吧
    2023-09-09
  • React Native 圖片查看組件的方法

    React Native 圖片查看組件的方法

    這篇文章主要介紹了React Native 圖片查看組件的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • Remix集成antd和pro-components的過程示例

    Remix集成antd和pro-components的過程示例

    這篇文章主要為大家介紹了Remix集成antd和pro-components的過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • React技巧之中斷map循環(huán)的方法詳解

    React技巧之中斷map循環(huán)的方法詳解

    這篇文章主要和大家來分享一下React的技巧之如何中斷map循環(huán),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下
    2023-06-06
  • 深入理解React高階組件

    深入理解React高階組件

    本篇文章主要介紹了深入理解React高階組件,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • React實(shí)現(xiàn)合成事件的源碼分析

    React實(shí)現(xiàn)合成事件的源碼分析

    React?中的事件,是對(duì)原生事件的封裝,叫做合成事件。抽象出一層合成事件,是為了做兼容,抹平不同瀏覽器之間的差異。本文將從事件綁定和事件觸發(fā)角度,帶大家解讀下源碼,感興趣的可以了解一下
    2022-12-12
  • React?Hook?Form?優(yōu)雅處理表單使用指南

    React?Hook?Form?優(yōu)雅處理表單使用指南

    這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • React 組件間的通信示例

    React 組件間的通信示例

    這篇文章主要介紹了React 組件間的通信示例,主要通信劃分為三種,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-06-06
  • React踩坑之a(chǎn)ntd輸入框rules中的required=true問題

    React踩坑之a(chǎn)ntd輸入框rules中的required=true問題

    這篇文章主要介紹了React踩坑之a(chǎn)ntd輸入框rules中的required=true問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 淺談React中的元素、組件、實(shí)例和節(jié)點(diǎn)

    淺談React中的元素、組件、實(shí)例和節(jié)點(diǎn)

    這篇文章主要介紹了淺談React中的元素、組件、實(shí)例和節(jié)點(diǎn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-02-02

最新評(píng)論