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

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

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

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

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

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

Fiber = {  memoizedState: Hook}

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

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

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

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

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

一、mountState

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

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

const hook = mountWorkInProgressHook();

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

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

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

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

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

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

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

return [hook.memoizedState, dispatch];

二、updateState

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

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

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

三、updateReducer

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

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

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

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

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

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

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

四、dispatchSetState

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

setLoading -> dispatch -> dispatchSetState

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

五、總結與思考

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

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

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

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

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

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

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

標簽:
  • 熱門焦點
  • Rust中的高吞吐量流處理

    作者 | Noz編譯 | 王瑞平本篇文章主要介紹了Rust中流處理的概念、方法和優化。作者不僅介紹了流處理的基本概念以及Rust中常用的流處理庫,還使用這些庫實現了一個流處理程序
  • Flowable工作流引擎的科普與實踐

    一.引言當我們在日常工作和業務中需要進行各種審批流程時,可能會面臨一系列技術和業務上的挑戰。手動處理這些審批流程可能會導致開發成本的增加以及業務復雜度的上升。在這
  • 之家push系統迭代之路

    前言在這個信息爆炸的互聯網時代,能夠及時準確獲取信息是當今社會要解決的關鍵問題之一。隨著之家用戶體量和內容規模的不斷增大,傳統的靠"主動拉"獲取信息的方式已不能滿足用
  • 三星獲批量產iPhone 15全系屏幕:蘋果史上最驚艷直屏

    按照慣例,蘋果將繼續在今年9月舉辦一年一度的秋季新品發布會,有傳言稱發布會將于9月12日舉行,屆時全新的iPhone 15系列將正式與大家見面,不出意外的話
  • 蘋果公司要求三星和LG Display生產「無邊框」OLED iPhone顯示屏

    據 The Elec 報道,蘋果已要求其供應商為未來的 iPhone 型號開發「無邊框」OLED 顯示面板。蘋果顯然已要求三星和 LG Display 開發新的 OLED 顯示面
  • iQOO 11S新品發布會

    iQOO將在7月4日19:00舉行新品發布會,推出杭州亞運會電競賽事官方用機iQOO 11S。
  • OPPO K11采用全方位護眼屏:三大護眼能力減輕視覺疲勞

    日前OPPO官方宣布,全新的OPPO K11將于7月25日正式發布,將主打旗艦影像,和同檔位競品相比,其最大的賣點就是將配備索尼IMX890主攝,堪稱是2000檔位影像表
  • 朋友圈可以修改可見范圍了 蘋果用戶可率先體驗

    近日,iOS用戶迎來微信8.0.27正式版更新,除了可更換二維碼背景外,還新增了多項實用功能。在新版微信中,朋友圈終于可以修改可見范圍,簡單來說就是已發布的朋友圈
  • 榮耀Magicbook V 14 2021曙光藍版本正式開售,擁有觸摸屏

    榮耀 Magicbook V 14 2021 曙光藍版本正式開售,搭載 i7-11390H 處理器與 MX450 顯卡,配備 16GB 內存與 512GB SSD,重 1.48kg,厚 14.5mm,具有 1.5mm 鍵盤鍵程、
Top 主站蜘蛛池模板: 文水县| 绵竹市| 上饶县| 东乌| 鄂托克旗| 杨浦区| 马公市| 崇左市| 清流县| 宝兴县| 垦利县| 都兰县| 富阳市| 太康县| 福贡县| 余姚市| 克什克腾旗| 枝江市| 大埔县| 广汉市| 八宿县| 易门县| 大庆市| 宿迁市| 昌乐县| 淮南市| 句容市| 新河县| 康保县| 阳江市| 伽师县| 惠来县| 阳信县| 河曲县| 堆龙德庆县| 益阳市| 广平县| 大悟县| 宁河县| 邵阳县| 西乌|