日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

燒腦預警,這波心智負擔有點重,深度探討 useState 的實現原理

來源: 責編: 時間:2024-04-19 09:29:41 249觀看
導讀在前面的一篇文章中,我們介紹了 Fiber 的詳細屬性所代表的含義。在函數式組件中,其中與 hook 相關的屬性為 memoizedState。Fiber = { memoizedState: Hook}Fiber.memoizedState 是一個鏈表的起點,該鏈表的節點信息為。

I3528資訊網——每日最新資訊28at.com

在前面的一篇文章中,我們介紹了 Fiber 的詳細屬性所代表的含義。在函數式組件中,其中與 hook 相關的屬性為 memoizedState。I3528資訊網——每日最新資訊28at.com

Fiber = {  memoizedState: Hook}

Fiber.memoizedState 是一個鏈表的起點,該鏈表的節點信息為。I3528資訊網——每日最新資訊28at.com

export type Hook = {  memoizedState: any,  baseState: any,  baseQueue: Update<any, any> | null,  queue: any,  next: Hook | null,}

useState 調用分為兩個階段,一個是初始化階段,一個是更新階段。當我們在 beginWork 中調用 renderWithHooks 時,通過判斷 Fiber.memozedState 是否有值來分辨當前執行屬于初始階段還是更新階段。I3528資訊網——每日最新資訊28at.com

ReactCurrentDispatcher.current =  current === null || current.memoizedState === null    ? HooksDispatcherOnMount    : HooksDispatcherOnUpdate;

在 react 模塊中,我們可以看到 useState 的源碼非常簡單。I3528資訊網——每日最新資訊28at.com

export function useState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  const dispatcher = resolveDispatcher();  return dispatcher.useState(initialState);}

這里的 dispatcher,其實就是我們在 react-reconciler 中判斷好的 ReactCurrentDispatcher.currenthook 的初始化方法掛載在 HooksDispatcherOnMount 上。I3528資訊網——每日最新資訊28at.com

const HooksDispatcherOnMount: Dispatcher = {  readContext,  useCallback: mountCallback,  useContext: readContext,  useEffect: mountEffect,  useImperativeHandle: mountImperativeHandle,  useLayoutEffect: mountLayoutEffect,  useInsertionEffect: mountInsertionEffect,  useMemo: mountMemo,  useReducer: mountReducer,  useRef: mountRef,  useState: mountState,  useDebugValue: mountDebugValue,  useDeferredValue: mountDeferredValue,  useTransition: mountTransition,  useMutableSource: mountMutableSource,  useSyncExternalStore: mountSyncExternalStore,  useId: mountId,  unstable_isNewReconciler: enableNewReconciler,};

hook 的更新方法掛載在 HooksDispatcherOnUpdate 上。I3528資訊網——每日最新資訊28at.com

const HooksDispatcherOnUpdate: Dispatcher = {  readContext,  useCallback: updateCallback,  useContext: readContext,  useEffect: updateEffect,  useImperativeHandle: updateImperativeHandle,  useInsertionEffect: updateInsertionEffect,  useLayoutEffect: updateLayoutEffect,  useMemo: updateMemo,  useReducer: updateReducer,  useRef: updateRef,  useState: updateState,  useDebugValue: updateDebugValue,  useDeferredValue: updateDeferredValue,  useTransition: updateTransition,  useMutableSource: updateMutableSource,  useSyncExternalStore: updateSyncExternalStore,  useId: updateId,  unstable_isNewReconciler: enableNewReconciler,};

因此,在初始化時,useState 調用的是 mountState,在更新時,useState 調用的是 updateStateI3528資訊網——每日最新資訊28at.com

一、mountState

mountState 的源碼如下:I3528資訊網——每日最新資訊28at.com

function mountState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  const hook = mountWorkInProgressHook();  if (typeof initialState === 'function') {    initialState = initialState();  }  hook.memoizedState = hook.baseState = initialState;  const queue: UpdateQueue<S, BasicStateAction<S>> = {    pending: null,    lanes: NoLanes,    dispatch: null,    lastRenderedReducer: basicStateReducer,    lastRenderedState: (initialState: any),  };  hook.queue = queue;  const dispatch: Dispatch<    BasicStateAction<S>,  > = (queue.dispatch = (dispatchSetState.bind(    null,    currentlyRenderingFiber,    queue,  ): any));  return [hook.memoizedState, dispatch];}

理解這個源碼的關鍵在第一行代碼。I3528資訊網——每日最新資訊28at.com

const hook = mountWorkInProgressHook();

react 在 ReactFiberHooks.new.js 模塊全局中創建了如下三個變量。I3528資訊網——每日最新資訊28at.com

let currentlyRenderingFiber: Fiber = (null: any);let currentHook: Hook | null = null;let workInProgressHook: Hook | null = null;

currentlyRenderingFiber 表示當前正在 render 中的 Fiber 節點。currentHook 表示當前 Fiber 的鏈表。I3528資訊網——每日最新資訊28at.com

workInProgressHook 表示當前正在構建中的新鏈表。I3528資訊網——每日最新資訊28at.com

mountWorkInProgressHook 方法會創建當前這個 mountState 執行所產生的 hook 鏈表節點。I3528資訊網——每日最新資訊28at.com

function mountWorkInProgressHook(): Hook {  const hook: Hook = {    memoizedState: null,    baseState: null,    baseQueue: null,    queue: null,    next: null,  };  if (workInProgressHook === null) {    // 作為第一個節點    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;  } else {    // 添加到鏈表的下一個節點    workInProgressHook = workInProgressHook.next = hook;  }  // 返回當前節點  return workInProgressHook;}

hook 節點的 queue 表示一個新的鏈表結構,用于存儲針對同一個 state 的多次 update 操作。,.pending 指向下一個 update 鏈表節點。此時因為是初始化操作,因此值為 null,此時我們會先創建一個 queue。I3528資訊網——每日最新資訊28at.com

const queue: UpdateQueue<S, BasicStateAction<S>> = {  pending: null,  lanes: NoLanes,  dispatch: null,  lastRenderedReducer: basicStateReducer,  lastRenderedState: (initialState: any),};hook.queue = queue;

此時,dispatch 還沒有賦值。在接下來我們調用了 dispatchSetState,我們待會兒來詳細介紹這個方法,他會幫助 queue.pending 完善鏈表結構或者進入調度階段,并返回了當前 hook 需要的 dispatch 方法。I3528資訊網——每日最新資訊28at.com

const dispatch: Dispatch<  BasicStateAction<S>,> = (queue.dispatch = (dispatchSetState.bind(  null,  currentlyRenderingFiber,  queue,): any));

最后將初始化之后的緩存值和操作方法通過數組的方式返回。I3528資訊網——每日最新資訊28at.com

return [hook.memoizedState, dispatch];

二、updateState

更新時,將會調用 updateState 方法,他的代碼非常簡單,就是直接調用了一下 updateReducer。I3528資訊網——每日最新資訊28at.com

function updateState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {  return updateReducer(basicStateReducer, (initialState: any));}

這里的需要注意的是有一個模塊中的全局方法 basicStateReducer,該方法執行會結合傳入的 action 返回最新的 state 值。I3528資訊網——每日最新資訊28at.com

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {  // $FlowFixMe: Flow doesn't like mixed types  return typeof action === 'function' ? action(state) : action;}

代碼中區分的情況是 useState 與 useReducer 的不同。useState 傳入的是值,而 useReducer 傳入的是函數I3528資訊網——每日最新資訊28at.com

三、updateReducer

updateReducer 的代碼量稍微多了一些,不過他的主要邏輯是計算出最新的 state 值。I3528資訊網——每日最新資訊28at.com

當我們使用 setState 多次調用 dispatch 之后, 在 Hook 節點的 hook.queue 上會保存一個循環鏈表用于存儲上一次的每次調用傳入的 state 值,updateReducer 的主要邏輯就是遍歷該循環鏈表,并計算出最新值。I3528資訊網——每日最新資訊28at.com

此時首先會將 queue.pending 的鏈表賦值給 hook.baseQueue,然后置空 queue.pending。I3528資訊網——每日最新資訊28at.com

const pendingQueue = queue.pending;current.baseQueue = baseQueue = pendingQueue;queue.pending = null;

然后通過 while 循環遍歷 hook.baseQueue 通過 reducer 計算出最新的 state 值。I3528資訊網——每日最新資訊28at.com

// 簡化版代碼const first = baseQueue.next;if (first !== null) {  let newState = current.baseState;  let update = first;  do {    // 執行每一次更新,去更新狀態    const action = update.action;    newState = reducer(newState, action);    update = update.next;  } while (update !== null && update !== first);  hook.memoizedState = newState;}

最后再返回。I3528資訊網——每日最新資訊28at.com

const dispatch: Dispatch<A> = (queue.dispatch: any);return [hook.memoizedState, dispatch];

四、dispatchSetState

當我們調用 setState 時,最終調用的是 dispatchSetState 方法。I3528資訊網——每日最新資訊28at.com

setLoading -> dispatch -> dispatchSetState

該方法有兩個邏輯,一個是同步調用,一個是并發模式下的異步調用。I3528資訊網——每日最新資訊28at.com

同步調用時,主要的目的在于創建 hook.queue.pending 指向的環形鏈表。I3528資訊網——每日最新資訊28at.com

首先我們要創建一個鏈表節點,該節點我們稱之為 update。I3528資訊網——每日最新資訊28at.com

const lane = requestUpdateLane(fiber);const update: Update<S, A> = {  lane,  action,  hasEagerState: false,  eagerState: null,  next: (null: any),};

然后會判斷是否在 render 的時候調用了該方法。I3528資訊網——每日最新資訊28at.com

if (isRenderPhaseUpdate(fiber)) {  enqueueRenderPhaseUpdate(queue, update);} else {

isRenderPhaseUpdate 用于判斷當前是否是在 render 時調用,他的邏輯也非常簡單。I3528資訊網——每日最新資訊28at.com

function isRenderPhaseUpdate(fiber: Fiber) {  const alternate = fiber.alternate;  return (    fiber === currentlyRenderingFiber ||    (alternate !== null && alternate === currentlyRenderingFiber)  );}

這里需要重點關注是 enqueueRenderPhaseUpdate 是如何創建環形鏈表的。他的代碼如下:I3528資訊網——每日最新資訊28at.com

function enqueueRenderPhaseUpdate<S, A>(  queue: UpdateQueue<S, A>,  update: Update<S, A>,) {  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;  const pending = queue.pending;  if (pending === null) {    update.next = update;  } else {    update.next = pending.next;    pending.next = update;  }  queue.pending = update;}

我們用圖示來表達一下這個邏輯,光看代碼可能理解起來比較困難。I3528資訊網——每日最新資訊28at.com

當只有一個 update 節點時。I3528資訊網——每日最新資訊28at.com

I3528資訊網——每日最新資訊28at.com

新增一個:I3528資訊網——每日最新資訊28at.com

I3528資訊網——每日最新資訊28at.com

再新增一個:I3528資訊網——每日最新資訊28at.com

I3528資訊網——每日最新資訊28at.com

在后續的邏輯中,會面臨的一種情況是當渲染正在發生時,收到了來自并發事件的更新,我們需要等待直到當前渲染結束或中斷再將其加入到 Fiber/Hook 隊列。因此React 需要一個數組來存儲這些更新,代碼邏輯如下:I3528資訊網——每日最新資訊28at.com

const concurrentQueues: Array<any> = [];let concurrentQueuesIndex = 0;
function enqueueUpdate(  fiber: Fiber,  queue: ConcurrentQueue | null,  update: ConcurrentUpdate | null,  lane: Lane,) {  concurrentQueues[concurrentQueuesIndex++] = fiber;  concurrentQueues[concurrentQueuesIndex++] = queue;  concurrentQueues[concurrentQueuesIndex++] = update;  concurrentQueues[concurrentQueuesIndex++] = lane;  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);  fiber.lanes = mergeLanes(fiber.lanes, lane);  const alternate = fiber.alternate;  if (alternate !== null) {    alternate.lanes = mergeLanes(alternate.lanes, lane);  }}

在這個基礎之上,React 就有機會處理那些不會立即導致重新渲染的更新進入隊列。如果后續有更高優先級的更新出現,將會重新對其進行排序。I3528資訊網——每日最新資訊28at.com

export function enqueueConcurrentHookUpdateAndEagerlyBailout<S, A>(  fiber: Fiber,  queue: HookQueue<S, A>,  update: HookUpdate<S, A>,): void {  // This function is used to queue an update that doesn't need a rerender. The  // only reason we queue it is in case there's a subsequent higher priority  // update that causes it to be rebased.  const lane = NoLane;  const concurrentQueue: ConcurrentQueue = (queue: any);  const concurrentUpdate: ConcurrentUpdate = (update: any);  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);}

dispatchSetState 的邏輯中,符合條件就會執行該函數。I3528資訊網——每日最新資訊28at.com

if (is(eagerState, currentState)) {  // Fast path. We can bail out without scheduling React to re-render.  // It's still possible that we'll need to rebase this update later,  // if the component re-renders for a different reason and by that  // time the reducer has changed.  // TODO: Do we still need to entangle transitions in this case?  enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);  return;}

很顯然,這就是并發更新的邏輯,代碼會最終調用 scheduleUpdateOnFiber,該方法是由 react-reconciler 提供,他后續會將任務帶入到 scheduler 中調度。I3528資訊網——每日最新資訊28at.com

// 與 enqueueConcurrentHookUpdateAndEagerlyBailout 方法邏輯// 但會返回 root 節點const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);const eventTime = requestEventTime();scheduleUpdateOnFiber(root, fiber, lane, eventTime);entangleTransitionUpdate(root, queue, lane);

五、總結與思考

這就是 useState 的實現原理。其中包含了大量的邏輯操作,可能跟我們在使用時所想的那樣有點不太一樣。這里大量借助了閉包和鏈表結構來完成整個構想。I3528資訊網——每日最新資訊28at.com

這個邏輯里面也會有大量的探討存在于大廠面試的過程中。例如I3528資訊網——每日最新資訊28at.com

  • 為什么不能把 hook 的寫法放到 if 判斷中去。
  • setState 的合并操作是如何做到的。
  • hook 鏈表和 queue.pending 的環狀鏈表都應該如何理解?
  • setState 之后,為什么無法直接拿到最新值,徹底消化了之后這些問題都能很好的得到解答。

本文鏈接:http://m.www897cc.com/showinfo-26-84038-0.html燒腦預警,這波心智負擔有點重,深度探討 useState 的實現原理

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: C# 操作 Redis 的五種常見方法

下一篇: 前端實現空閑時注銷登錄,so easy!

標簽:
  • 熱門焦點
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
亚洲精品影院| 一区二区三区在线观看欧美 | 欧美精品免费看| 欧美精品在线一区二区三区| 国产精品h在线观看| 国产亚洲成av人在线观看导航| 在线观看欧美日韩国产| 99视频+国产日韩欧美| 羞羞视频在线观看欧美| 蜜桃av一区二区| 国产精品国产自产拍高清av| 国产一区二区三区高清| 亚洲三级毛片| 欧美一区二区三区四区高清| 欧美激情精品久久久久久| 国产精品美女久久久浪潮软件| 狠狠色综合播放一区二区| 亚洲久色影视| 久久精品欧洲| 欧美性事在线| 亚洲国产日韩欧美在线图片| 亚洲欧美影院| 欧美激情在线免费观看| 国产在线欧美日韩| 亚洲视频精品| 欧美成人国产| 国产一区二区三区电影在线观看| 日韩视频一区二区| 久久久水蜜桃| 国产精品日本欧美一区二区三区| 亚洲欧洲精品一区二区三区不卡| 羞羞视频在线观看欧美| 欧美日韩调教| 亚洲国产另类久久精品| 欧美在线免费视频| 欧美性一区二区| 亚洲国产日韩欧美在线动漫| 欧美在线综合| 国产精品豆花视频| 亚洲欧洲日本专区| 久久久免费av| 国产精品综合av一区二区国产馆| 日韩视频在线播放| 久久综合五月| 国产一区在线看| 亚洲欧美另类国产| 欧美精品在线播放| 亚洲国产精品国自产拍av秋霞| 欧美一级成年大片在线观看| 欧美色图麻豆| 亚洲毛片在线看| 美国成人毛片| 国产一区二区精品| 性做久久久久久| 国产精品久久久久久久第一福利| 亚洲欧洲日产国码二区| 老司机免费视频久久| 国产区精品在线观看| 亚洲一区二区三区中文字幕在线| 欧美精品少妇一区二区三区| 在线日韩中文| 久久婷婷久久| 国内精品久久久| 欧美与欧洲交xxxx免费观看| 国产精品免费福利| 亚洲一卡久久| 欧美亚一区二区| 亚洲无限av看| 欧美午夜在线视频| 一区二区三区久久精品| 欧美日本国产精品| 日韩小视频在线观看| 欧美精品1区2区3区| 亚洲激情影院| 欧美国产精品专区| 亚洲精品国产精品乱码不99按摩| 欧美xart系列高清| 亚洲国产日韩在线| 欧美v国产在线一区二区三区| 亚洲缚视频在线观看| 欧美 日韩 国产一区二区在线视频| 在线观看三级视频欧美| 免费国产一区二区| 最新亚洲一区| 欧美日韩国产在线播放网站| 一区二区三区欧美成人| 国产精品福利久久久| 亚洲制服欧美中文字幕中文字幕| 国产精品久久亚洲7777| 欧美一区国产二区| 黄色日韩精品| 免费久久99精品国产自| 亚洲日韩成人| 欧美日韩福利在线观看| 宅男噜噜噜66一区二区66| 欧美午夜激情视频| 午夜精品一区二区三区四区 | 欧美另类69精品久久久久9999| 亚洲六月丁香色婷婷综合久久| 欧美日本亚洲| 亚洲一区二区av电影| 国产精品一区一区| 久久久久国产精品一区二区| 亚洲国产高清在线| 欧美日韩精品综合在线| 亚洲男女自偷自拍图片另类| 国产视频精品网| 久久亚洲欧美| 亚洲精品中文字幕有码专区| 欧美日韩一区高清| 午夜一区二区三区在线观看| 韩国av一区二区三区| 欧美国产视频在线| 亚洲已满18点击进入久久| 国产亚洲亚洲| 欧美黄色成人网| 亚洲中字黄色| 激情视频亚洲| 欧美日韩成人综合在线一区二区| 亚洲免费人成在线视频观看| 国内精品嫩模av私拍在线观看| 免费看黄裸体一级大秀欧美| 夜夜嗨av一区二区三区四区 | 亚洲精品久久久久久久久久久久久| 国产精品电影观看| 久久五月激情| 在线一区二区三区四区| 国产亚洲精品久| 欧美激情精品| 欧美一级欧美一级在线播放| 在线视频成人| 国产精品看片你懂得| 老司机精品视频一区二区三区| 一区二区三区**美女毛片| 亚洲欧洲在线免费| 激情成人中文字幕| 亚洲一区二区三区在线播放| 国内精品国产成人| 国产精品日韩欧美| 欧美国产日韩a欧美在线观看| 欧美mv日韩mv国产网站app| 国产精品久久久久毛片大屁完整版 | 国语自产精品视频在线看一大j8| 99国产一区| 欧美成人精品一区二区三区| 国产日韩欧美综合一区| 亚洲精品国产精品国自产观看| 一本到12不卡视频在线dvd| 一区二区精品在线| 久久天堂精品| 国产日产欧美一区| 亚洲一区二区在线视频| 免费日韩av| 一区二区国产日产| 国产真实久久| 欧美性做爰毛片| 欧美成人精品福利| 亚洲免费一在线| 91久久久久| 国产综合网站| 女人色偷偷aa久久天堂| 亚洲国产人成综合网站| 欧美日韩精品免费观看视频| 午夜欧美大片免费观看| 狠狠爱综合网| 欧美视频在线播放| 久久久久国色av免费观看性色| 亚洲欧洲一区二区在线播放| 国产美女精品在线| 久久久精品999| 国产日韩亚洲欧美| 国产美女精品人人做人人爽| 麻豆91精品| 欧美一级免费视频| 亚洲网站在线| 日韩视频免费在线观看| 亚洲第一偷拍| 激情文学一区| 国产一区二区三区在线观看免费| 国产精品高清一区二区三区| 欧美美女喷水视频| 欧美成人一品| 免费不卡在线观看| 久久综合色天天久久综合图片| 久久国产黑丝| 欧美一区二区视频在线| 亚洲欧美日韩在线高清直播| 亚洲一区二区三区涩| 亚洲图片欧洲图片av| 一区二区欧美在线| 一本色道婷婷久久欧美| 亚洲精品在线电影| 亚洲精品国精品久久99热一| 亚洲激情欧美| 亚洲国产影院| 亚洲日韩第九十九页| 亚洲国产综合在线看不卡| 在线精品观看| 亚洲第一黄网| 亚洲国产成人在线视频| 亚洲国产欧洲综合997久久| 新67194成人永久网站| 欧美日本国产一区| 欧美一区久久|