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

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

面試官:useEffect和useLayoutEffect有什么區別?你能說說嗎?

來源: 責編: 時間:2023-12-13 17:01:27 243觀看
導讀Effect數據結構顧名思義,React底層在函數式組件的Fiber節點設計中帶入了hooks鏈表的概念(memorizedState),在此變量上專門存儲每一個函數式組件對應的鏈表。而對于副作用(useEffect or useLayoutEffect)來說,對應其hook類型

Effect數據結構

顧名思義,React底層在函數式組件的Fiber節點設計中帶入了hooks鏈表的概念(memorizedState),在此變量上專門存儲每一個函數式組件對應的鏈表。BoX28資訊網——每日最新資訊28at.com

而對于副作用(useEffect or useLayoutEffect)來說,對應其hook類型就是Effect。BoX28資訊網——每日最新資訊28at.com

單個的effect對象包括以下幾個屬性:BoX28資訊網——每日最新資訊28at.com

  • create: 傳入useEffect or useLayoutEffect函數的第一個參數,即回調函數;
  • destroy: 回調函數return的函數,在該effect銷毀的時候執行,渲染階段為undefined;
  • deps: 依賴項,改變重新執行副作用;
  • next: 指向下一個effect;
  • tag: effect的類型,區分是useEffect還是useLayoutEffect;

單純看這些字段,和平時使用層面來聯想還是很通俗易懂的,這里還是補充一下hooks鏈表的概念,有如下的例子:BoX28資訊網——每日最新資訊28at.com

const Hello = () => {    const [ text, setText ] = useState('hello')    useEffect(() => {        console.log('effect1')        return () => {            console.log('destory1');        }    })    useLayoutEffect(() => {        console.log('effect2')        return () => {            console.log('destory2');        }    })    return <div>effect</div>}

掛載到Hello組件fiber上memoizedState如下:BoX28資訊網——每日最新資訊28at.com

圖片圖片BoX28資訊網——每日最新資訊28at.com

可以看到,打印出來結果和組件中聲明hook的順序是一樣的,不難看出這是一個鏈表,這也是為什么react hook要求hook的使用不能放在條件分支語句中的原因,如果第一次mount走的是A情況,第二次updateMount走的是B情況,就會出現hooks鏈表混亂的情況,保證官方范式是比較重要的原因。BoX28資訊網——每日最新資訊28at.com

Hook

從上圖的例子中可以看到,memorizedState的值會根據不同hook來決定。BoX28資訊網——每日最新資訊28at.com

  • 使用useState時,memorizedState對應是string(hello);
  • 使用useEffect和useLayoutEffect,對應的是Effect;

Hook類型如下:BoX28資訊網——每日最新資訊28at.com

export type Hook = {     memoizedState: any, // Hook 自身維護的狀態     baseQueue: any,    baseState: any,    queue: UpdateQueue<any, any> | null, // Hook 自身維護的更新隊列     next: Hook | null, // next 指向下一個 Hook };

創建副作用流程

基于上面的數據結構,對于use(Layout)Effect來說,React做的事情就是BoX28資訊網——每日最新資訊28at.com

  • render階段:函數組件開始渲染的時候,創建出對應的hook鏈表掛載到workInProgress的memoizedState上,并創建effect鏈表,也就是掛載到對應的fiber節點上,但是基于上次和本次依賴項的比較結果, 創建的effect是有差異的。這一點暫且可以理解為:依賴項有變化,effect可以被處理,否則不會被處理。
  • commit階段:異步調度useEffect或者同步處理useLayoutEffect的effect。等到commit階段完成后,更新應用到頁面上之后,開始處理useEffect產生的effect,或是直接處理commit階段同步執行阻塞頁面更新的useLayoutEffect產生的effect。

第二點提到了一個重點,就是useEffect和useLayoutEffect的執行時機不一樣,前者被異步調度,當頁面渲染完成后再去執行,不會阻塞頁面渲染。 后者是在commit階段新的DOM準備完成,但還未渲染到屏幕之前,同步執行。BoX28資訊網——每日最新資訊28at.com

創建effect鏈表

useEffect的工作是在currentlyRenderingFiber加載當前的hook,具體流程就是判斷當前fiber是否已經存在hook(就是判斷fiber.memoizedState),存在的話則創建一個effect hook到鏈表的最后,也就是.next,沒有的話則創建一個memoizedState。BoX28資訊網——每日最新資訊28at.com

先看一下創建一個Effect的入口函數:BoX28資訊網——每日最新資訊28at.com

function mountEffect(    create: () => (() => void) | void,    deps: Array<mixed> | void | null): void {    return mountEffectImpl(        UpdateEffect | PassiveEffect,        HookPassive,        create,        deps,    );};

可以看到本質上是調用了mountEffectImpl函數,傳了上一節所說的Effect type中的字段,這里有個問題,為什么destroy沒傳呢?獲取上一次effect的destroy函數,也就是useEffect回調中return的函數,在創建階段是第一次,所以為undefined。BoX28資訊網——每日最新資訊28at.com

這里看一下創建階段調用的mountEffectImpl函數:BoX28資訊網——每日最新資訊28at.com

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {  // 創建hook對象  const hook = mountWorkInProgressHook();  // 獲取依賴  const nextDeps = deps === undefined ? null : deps;  // 為fiber打上副作用的effectTag  currentlyRenderingFiber.flags |= fiberFlags;  // 創建effect鏈表,掛載到hook的memoizedState上和fiber的updateQueue  hook.memoizedState = pushEffect(    HookHasEffect | hookFlags,    create,    undefined,    nextDeps,  );}

接下來我們都知道,React或Vue都是狀態改變導致頁面重渲染,而useEffect or useLayoutEffect都會會根據deps變化重新執行,所以猜都猜得到,在更新時調用的updateEffectImpl函數,對比mountEffectImpl函數多出來的一部分內容其實就是對比上一次的Effect的依賴變化,以及執行上一次Effect中的destroy部分內容~代碼如下:BoX28資訊網——每日最新資訊28at.com

function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {  const hook = updateWorkInProgressHook();  const nextDeps = deps === undefined ? null : deps;  let destroy = undefined;  if (currentHook !== null) {    // 從currentHook中獲取上一次的effect    const prevEffect = currentHook.memoizedState;    // 獲取上一次effect的destory函數,也就是useEffect回調中return的函數    destroy = prevEffect.destroy;    if (nextDeps !== null) {      const prevDeps = prevEffect.deps;      // 比較前后依賴,push一個不帶HookHasEffect的effect      if (areHookInputsEqual(nextDeps, prevDeps)) {        pushEffect(hookFlags, create, destroy, nextDeps);        return;      }    }  }  currentlyRenderingFiber.flags |= fiberFlags;  // 如果前后依賴有變,在effect的tag中加入HookHasEffect  // 并將新的effect更新到hook.memoizedState上  hook.memoizedState = pushEffect(    HookHasEffect | hookFlags,    create,    destroy,    nextDeps,  );}

可以看到在mountEffectImpl和updateEffectImpl中,最后的結果走向都是pushEffect函數,它的工作很純粹,就是創建出effect對象,把對象掛到鏈表中。BoX28資訊網——每日最新資訊28at.com

pushEffect代碼如下:BoX28資訊網——每日最新資訊28at.com

function pushEffect(tag, create, destroy, deps) {  // 創建effect對象  const effect: Effect = {    tag,    create,    destroy,    deps,    // Circular    next: (null: any),  };  // 從workInProgress節點上獲取到updateQueue,為構建鏈表做準備  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);  if (componentUpdateQueue === null) {    // 如果updateQueue為空,把effect放到鏈表中,和它自己形成閉環    componentUpdateQueue = createFunctionComponentUpdateQueue();    // 將updateQueue賦值給WIP節點的updateQueue,實現effect鏈表的掛載    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);    componentUpdateQueue.lastEffect = effect.next = effect;  } else {    // updateQueue不為空,將effect接到鏈表的后邊    const lastEffect = componentUpdateQueue.lastEffect;    if (lastEffect === null) {      componentUpdateQueue.lastEffect = effect.next = effect;    } else {      const firstEffect = lastEffect.next;      lastEffect.next = effect;      effect.next = firstEffect;      componentUpdateQueue.lastEffect = effect;    }  }  return effect;}

這里的主要邏輯其實就是本節開頭所說的,區分兩種情況,鏈表為空或鏈表存在的情況,值得一提的是這里的updateQueue是一個環形鏈表。BoX28資訊網——每日最新資訊28at.com

以上,就是effect鏈表的構建過程。我們可以看到,effect對象創建出來最終會以兩種形式放到兩個地方:單個的effect,放到hook.memorizedState上;環狀的effect鏈表,放到fiber節點的updateQueue中。兩者各有用途,前者的effect會作為上次更新的effect,為本次創建effect對象提供參照(對比依賴項數組),后者的effect鏈表會作為最終被執行的主體,帶到commit階段處理。BoX28資訊網——每日最新資訊28at.com

提交階段

commitRoot

當我們完成更新,進入提交重渲染視圖時,主要在commitRoot函數中執行,而在這之前創建Effect以及插入到hooks鏈表中,useEffect和useLayoutEffect其實做的都是一樣的,也是共用的,在提交階段,我們會看出兩者執行時機不同的實現點。BoX28資訊網——每日最新資訊28at.com

// src/react-reconciler/src/ReactFiberWorkLoop.jsfunction commitRoot(root) {  // 已經完成構建的fiber,上面會包括hook信息  const { finishedWork } = root;  // 如果存在useEffect或者useLayoutEffect  if ((finishedWork.flags & Passive) !== NoFlags) {    if (!rootDoesHavePassiveEffect) {      rootDoesHavePassiveEffect = true;      // 開啟下一個宏任務      requestIdleCallback(flushPassiveEffect);    }  }  console.log('start commit.');    // 判斷自己身上有沒有副作用  const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;  // 如果自己的副作用或者子節點有副作用就進行DOM操作  if (rootHasEffect) {    console.log('DOM執行完畢');      commitMutationEffectsOnFiber(finishedWork, root);        // 執行layout Effect      console.log('開始執行layoutEffect');    commitLayoutEffects(finishedWork, root);    if (rootDoesHavePassiveEffect) {      rootDoesHavePassiveEffect = false;      rootWithPendingPassiveEffects = root;    }  }  // 等DOM變更之后,更改root中current的指向  root.current = finishedWork;}

這里的rootDoesHavePassiveEffect是核心判斷點,還記得Effect類型中的tag參數嗎?就是依靠這個參數來標識區分useEffect和useLayoutEffect的。BoX28資訊網——每日最新資訊28at.com

rootDoesHavePassiveEffect === false,則執行宏任務,將Effect副作用推入宏任務執行棧中。我們可以簡單理解成useEffect的回調函數包裝在了requestIdleCallback中去異步執行,根據fiber的知識接下來會去走瀏覽器當前幀是否有空余時間來判斷副作用函數的執行時機。BoX28資訊網——每日最新資訊28at.com

繼續往下走,如果rootHasEffect === true,代表有副作用,如果是useEffect,副作用已經在上面進入宏任務隊列了,所以如果是useLayoutEffect,就會在這個條件中去執行,所以在這里我們可以理解到那一句"useEffect和useLayoutEffect的區別是,前者會異步執行副作用函數不會阻塞頁面更新,后者會立即執行副作用函數,會阻塞頁面更新,不適合寫入復雜邏輯。"的原因了。BoX28資訊網——每日最新資訊28at.com

結尾

useEffect與useLayoutEffect十分相似,就連簽名都一樣,不同之處就在于前者會在瀏覽器繪制后延遲執行,而后者會在所有DOM變更之后同步調用effect,希望你看到這里,可以對于這個結論的來源有一定的了解和學習,希望可以幫到你~BoX28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-44381-0.html面試官:useEffect和useLayoutEffect有什么區別?你能說說嗎?

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

上一篇: 改善代碼質量,試試這十種方法

下一篇: Python設計模式,裝飾器模式

標簽:
  • 熱門焦點
  • 5月安卓手機好評榜:魅族20 Pro奪冠

    性能榜和性價比榜之后,我們來看最后的安卓手機好評榜,數據來源安兔兔評測,收集時間2023年5月1日至5月31日,僅限國內市場。第一名:魅族20 Pro好評率:97.50%不得不感慨魅族老品牌還
  • 如何使用JavaScript創建一只圖像放大鏡?

    譯者 | 布加迪審校 | 重樓如果您曾經瀏覽過購物網站,可能遇到過圖像放大功能。它可以讓您放大圖像的特定區域,以便瀏覽。結合這個小小的重要功能可以大大改善您網站的用戶體驗
  • 三萬字盤點 Spring 九大核心基礎功能

    大家好,我是三友~~今天來跟大家聊一聊Spring的9大核心基礎功能。話不多說,先上目錄:圖片友情提示,本文過長,建議收藏,嘿嘿嘿!一、資源管理資源管理是Spring的一個核心的基礎功能,不
  • 破圈是B站頭上的緊箍咒

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之每年的暑期檔都少不了瞄準追劇女孩們的古偶劇集,2021年有優酷的《山河令》,2022年有愛奇藝的《蒼蘭訣》,今年卻輪到小破站抓住了追
  • 得物寵物生意「狂飆」,發力“它經濟”

    作者|花花小萌主近日,得物宣布正式上線寵物鑒別,通過得物App內的&ldquo;在線鑒別&rdquo;,可找到鑒別寵物的選項。通過上傳自家寵物的部位細節,就能收獲擁有專業資質認證的得物鑒
  • 一條抖音4億人圍觀 ! 這家MCN比無憂傳媒還野

    作者:Hiu 來源:互聯網品牌官01 擦邊少女空降熱搜,幕后推手曝光被網友譽為&ldquo;純欲天花板&rdquo;的女網紅井川里予,近期因為一組哥特風照片登上熱搜,引發了一場互聯網世界關于
  • 三星電子Q2營收60萬億韓元 存儲業務營收同比仍下滑超過50%

    7月27日消息,據外媒報道,從三星電子所發布的財報來看,他們主要利潤來源的存儲芯片業務在今年二季度仍不樂觀,營收同比仍在大幅下滑,所在的設備解決方案
  • 2299元起!iQOO Pad明晚首銷:性能最強天璣平板

    5月23日,iQOO如期舉行了新品發布會,除了首發安卓最強旗艦處理器的iQOO Neo8系列新機外,還在發布會上推出了旗下首款平板電腦——iQOO Pad,其最大的賣點
  • 蘋果MacBook Pro 2021測試:仍不支持平滑滾動

    據10月30日9to5 Mac 消息報道,蘋果新的 14 英寸和 16 英寸 MacBook Pro 2021 上市后獲得了不錯的評價,亮點包括行業領先的性能,令人印象深刻的電池續航,精美豐
Top 主站蜘蛛池模板: 二连浩特市| 佛冈县| 个旧市| 桃江县| 准格尔旗| 宁安市| 广灵县| 陵水| 德化县| 邢台市| 禹城市| 福州市| 红桥区| 景德镇市| 衡水市| 徐闻县| 高邮市| 文昌市| 台北市| 枞阳县| 武鸣县| 华坪县| 临朐县| 泾阳县| 乌什县| 邢台县| 旬阳县| 车险| 华池县| 天气| 卓尼县| 庆元县| 稷山县| 华宁县| 桂东县| 多伦县| 兴宁市| 太仓市| 津市市| 宁城县| 浏阳市|