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

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開(kāi)始實(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

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

  • hooks是如何存放數(shù)據(jù)的,以及一些hooks的規(guī)則。
  • 一次dispatch觸發(fā)的更新整體流程,雙緩存樹(shù)的運(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我們來(lái)講講hook在初始化和更新階段的區(qū)別。以及react是如何做到hook不能在條件語(yǔ)句和函數(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ù)來(lái)源

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。 我們首先來(lái)看看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é)果來(lái)推算這個(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就需要通過(guò)next屬性,將hook串聯(lián)起來(lái)。

在這個(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, 說(shuō)明是第一個(gè)hook執(zhí)行。所以我們將賦值workInProgressHook正在執(zhí)行的hook, 同時(shí)將FC fiberNodememoizedState指向第一個(gè)hook。此時(shí)就生成了如下圖的結(jié)構(gòu):

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

通過(guò)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)單,就是通過(guò)傳入的參數(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保存起來(lái),方便之后update使用。
  • 將生成的dispatch保存起來(lái),方便之后update使用。
// useState是可以觸發(fā)更新的
const queue = createUpdateQueue<State>();
hook.updateQueue = queue;
const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;

主要是看如何生成dispatch的邏輯,通過(guò)調(diào)用dispatchSetState它接受三個(gè)參數(shù),因?yàn)槲覀冃枰朗菑哪囊粋€(gè)fiberNode開(kāi)始調(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. 開(kāi)始調(diào)度
}

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

執(zhí)行dispatch后,開(kāi)始新一輪的調(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í)候,我們之前講過(guò)相當(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. 開(kāi)始調(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ù)從哪里來(lái)
  • 會(huì)有2種情況執(zhí)行,交互階段觸發(fā),render的時(shí)候觸發(fā)

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

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

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

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

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

updateWorkInProgressHook

更新階段hook的數(shù)據(jù)獲取是通過(guò)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é)如下:

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

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

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

我們曉得hook是不能在條件語(yǔ)句中執(zhí)行的。那是如何做到報(bào)錯(cuò)的呢?接下來(lái)我們根據(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,我們來(lái)分析一下。

  • 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ù)來(lái)源,我們現(xiàn)在得到數(shù)據(jù)了,那如何通過(guò)之前的數(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ù)

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

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

例如初始化的時(shí)候num的值為3, 通過(guò)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í)候,第二次更新后,雙緩存樹(shù)已經(jīng)建立。會(huì)利用之前右邊的4fiberNode tree,進(jìn)行下一輪渲染。

總結(jié)

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

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

相關(guān)文章

  • React的生命周期詳解

    React的生命周期詳解

    React是用于構(gòu)建用戶界面的JavaScript庫(kù)。本文詳細(xì)講解了React的生命周期,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 深入理解React Native原生模塊與JS模塊通信的幾種方式

    深入理解React Native原生模塊與JS模塊通信的幾種方式

    本篇文章主要介紹了深入理解React Native原生模塊與JS模塊通信的幾種方式,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-07-07
  • React?Fiber原理深入分析

    React?Fiber原理深入分析

    Fiber可以理解為一個(gè)執(zhí)行單元,每次執(zhí)行完一個(gè)執(zhí)行單元,React?Fiber就會(huì)檢查還剩多少時(shí)間,如果沒(méi)有時(shí)間則將控制權(quán)讓出去,然后由瀏覽器執(zhí)行渲染操作,這篇文章主要介紹了React?Fiber架構(gòu)原理剖析,需要的朋友可以參考下<BR>
    2023-01-01
  • 在Ant Design Pro登錄功能中集成圖形驗(yàn)證碼組件的方法步驟

    在Ant Design Pro登錄功能中集成圖形驗(yàn)證碼組件的方法步驟

    這篇文章主要介紹了在Ant Design Pro登錄功能中集成圖形驗(yàn)證碼組件的方法步驟,這里的登錄功能其實(shí)就是一個(gè)表單提交,實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,具體實(shí)例代碼跟隨小編一起看看吧
    2021-05-05
  • React聲明組件的方法總結(jié)

    React聲明組件的方法總結(jié)

    這篇文章主要給大家介紹了react聲明組件有哪幾種方法,各有什么不同,文章通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-11-11
  • React根據(jù)當(dāng)前頁(yè)面路由進(jìn)行自動(dòng)高亮示例代碼

    React根據(jù)當(dāng)前頁(yè)面路由進(jìn)行自動(dòng)高亮示例代碼

    要根據(jù)當(dāng)前頁(yè)面路由自動(dòng)高亮頂部菜單項(xiàng),可以使用 React Router 的 useLocation 鉤子來(lái)獲取當(dāng)前路徑,并根據(jù)路徑動(dòng)態(tài)設(shè)置菜單項(xiàng)的高亮效果,本文給大家介紹了一個(gè)完整的示例,展示如何根據(jù)當(dāng)前頁(yè)面路由自動(dòng)高亮頂部菜單項(xiàng),需要的朋友可以參考下
    2024-07-07
  • React 非父子組件傳參的實(shí)例代碼

    React 非父子組件傳參的實(shí)例代碼

    React 主要用于構(gòu)建UI,很多人認(rèn)為 React 是 MVC 中的 V(視圖)。本文給大家介紹React 非父子組件傳參的相關(guān)知識(shí),感興趣的朋友跟隨一起看看吧
    2021-04-04
  • React?狀態(tài)管理中的Jotai詳解

    React?狀態(tài)管理中的Jotai詳解

    Jotai是一個(gè)簡(jiǎn)單、靈活、高效的React狀態(tài)管理庫(kù),通過(guò)原子和派生狀態(tài)的設(shè)計(jì),使得狀態(tài)管理變得直觀和高效,它適用于小型應(yīng)用、組件庫(kù)和大型項(xiàng)目的局部狀態(tài)管理,且與TypeScript兼容,Jotai的社區(qū)正在壯大,提供了豐富的資源和支持,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • 詳解各版本React路由的跳轉(zhuǎn)的方法

    詳解各版本React路由的跳轉(zhuǎn)的方法

    這篇文章主要介紹了詳解各版本React路由的跳轉(zhuǎn)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • React中的useEffect四種用法分享

    React中的useEffect四種用法分享

    這篇文章主要給大家分享React中的useEffect四種用法,useEffect中 觸發(fā)更新,重復(fù)的 useEffect,依賴值觸發(fā)回調(diào),useEffect 的返回值,通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07

最新評(píng)論

绥阳县| 青岛市| 镇安县| 桃园市| 聊城市| 博兴县| 诸城市| 邹平县| 铜陵市| 云安县| 东至县| 金溪县| 赣榆县| 四平市| 桃江县| 白城市| 济阳县| 东乡县| 江西省| 牡丹江市| 讷河市| 永吉县| 紫阳县| 工布江达县| 泸西县| 龙川县| 军事| 上思县| 盐山县| 灵川县| 青龙| 罗江县| 凤阳县| 健康| 百色市| 邮箱| 嫩江县| 定陶县| 九寨沟县| 四会市| 边坝县|