AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

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

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

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

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

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

Fiber = {  memoizedState: Hook}

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

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

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

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

在 react 模塊中,我們可以看到 useState 的源碼非常簡單。gli28資訊網——每日最新資訊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 上。gli28資訊網——每日最新資訊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 上。gli28資訊網——每日最新資訊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 調用的是 updateStategli28資訊網——每日最新資訊28at.com

一、mountState

mountState 的源碼如下:gli28資訊網——每日最新資訊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];}

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

const hook = mountWorkInProgressHook();

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

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

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

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

mountWorkInProgressHook 方法會創建當前這個 mountState 執行所產生的 hook 鏈表節點。gli28資訊網——每日最新資訊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。gli28資訊網——每日最新資訊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 方法。gli28資訊網——每日最新資訊28at.com

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

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

return [hook.memoizedState, dispatch];

二、updateState

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

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

這里的需要注意的是有一個模塊中的全局方法 basicStateReducer,該方法執行會結合傳入的 action 返回最新的 state 值。gli28資訊網——每日最新資訊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 傳入的是函數gli28資訊網——每日最新資訊28at.com

三、updateReducer

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

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

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

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

然后通過 while 循環遍歷 hook.baseQueue 通過 reducer 計算出最新的 state 值。gli28資訊網——每日最新資訊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;}

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

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

四、dispatchSetState

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

setLoading -> dispatch -> dispatchSetState

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

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

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

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

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

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

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

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

這里需要重點關注是 enqueueRenderPhaseUpdate 是如何創建環形鏈表的。他的代碼如下:gli28資訊網——每日最新資訊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;}

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

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

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

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

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

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

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

在后續的邏輯中,會面臨的一種情況是當渲染正在發生時,收到了來自并發事件的更新,我們需要等待直到當前渲染結束或中斷再將其加入到 Fiber/Hook 隊列。因此React 需要一個數組來存儲這些更新,代碼邏輯如下:gli28資訊網——每日最新資訊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 就有機會處理那些不會立即導致重新渲染的更新進入隊列。如果后續有更高優先級的更新出現,將會重新對其進行排序。gli28資訊網——每日最新資訊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 的邏輯中,符合條件就會執行該函數。gli28資訊網——每日最新資訊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 中調度。gli28資訊網——每日最新資訊28at.com

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

五、總結與思考

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

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

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

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

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

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

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

標簽:
  • 熱門焦點
Top