成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

UseMemo依賴沒變,回調還會反復執行?

開發 前端
首先,我們要明確一點:「Hook依賴項變化,回調重新執行」是針對不同更新來說的。而我們的Demo中UseMemo回調雖然會執行幾千次,但他們都是同一次更新中執行的。

大家好,我卡頌。

經常使用React的同學都知道,有些hook被設計為:「依賴項數組 + 回調」的形式,比如:

  • useEffect
  • useMemo

通常來說,當「依賴項數組」中某些值變化后,回調會重新執行。

我們知道,React的寫法十分靈活,那么有沒有可能,在「依賴項數組」不變的情況下,回調依然重新執行?

本文就來探討一個這樣的場景。

描述下Demo

在這個示例中,存在兩個文件:

  • App.tsx
  • Lazy.tsx

在App.tsx中,會通過React.lazy的形式懶加載Lazy.tsx導出的組件:

// App.tsx

import { Suspense, lazy } from "react";

const LazyCpn = lazy(() => import("./Lazy"));

function App() {
  return (
    <Suspense fallback={<div>外層加載...</div>}>
      <LazyCpn />
    </Suspense>
  );
}

export default App;

Lazy.tsx導出的LazyComponent大體代碼如下:

// Lazy.tsx

function LazyComponent() {

  const ChildComponent = useMemo(() => {
   // ...省略邏輯
  }, []);

  return ChildComponent;
}

export default LazyComponent;

可以發現,LazyComponent組件的子組件是useMemo的返回值,而這個useMemo的依賴項是[](沒有依賴項),理論上來說useMemo的回調只會執行一次。

再來看看useMemo回調中的詳細代碼:

const ChildComponent = useMemo(() => {
  const LazyCpn = lazy(
    () => Promise.resolve({ default: () => <div>子組件</div>})
  )

  return (
    <Suspense fallback={<div>內層加載...</div>}>
      <LazyCpn />
    </Suspense>  
  );
}, []);

簡單來說,useMemo會返回一個「被Suspense包裹的懶加載組件」。

是不是看起來比較繞,沒關系,我們看看整個Demo的結構圖:

  • 整個應用有兩層Suspense,兩層React.lazy。
  • 第二層Suspense是useMemeo回調的返回值。

這里是在線Demo地址[1]

應用渲染的結果如下:

現在問題來了,如果我們在useMemo回調中打印個log,記錄下執行情況,那么log會打印多少次?

const ChildComponent = useMemo(() => {
  console.log("useMemo回調執行啦")
  // ...省略代碼
}, []);

再次重申,這個useMemo的依賴項是不會變的

在我的電腦中,log大概會打印4000~6000次,也就是說,useMemo回調會執行4000~6000次,即使依賴不變。

why?

原理分析

首先,我們要明確一點:「hook依賴項變化,回調重新執行」是針對不同更新來說的。

而我們的Demo中useMemo回調雖然會執行幾千次,但他們都是同一次更新中執行的。

如果你對這一點有疑問,可以在LazyComponent(也就是Demo中的第一層React.lazy)中增加2個log:

  • 一個在useEffect回調中。
  • 一個在LazyComponent render函數中。
function LazyComponent() {
  console.log("LazyComponent render")
  
  useEffect(() => {
    console.log("LazyComponent mount");
  }, []);

  const ChildComponent = useMemo(() => {
   // ...省略邏輯
  }, []);

  return ChildComponent;
}

會發現:

  • LazyComponent render執行次數和useMemo回調執行啦一致(都是幾千次)
  • LazyComponent mount只會執行一次

也就是說,LazyComponent組件會render幾千次,但只會首屏渲染一次。

而「hook依賴項變化,回調重新執行」這條規則,只適用于不同更新之間(比如「首屏渲染」和「再次更新」之間),不適用于同一次更新的不同render之間(比如Demo中是首屏渲染的幾千次render)。

搞明白上面這些,我們還得解答一個問題:為啥首屏渲染LazyComponent組件會render幾千次?

unwind機制

在正常情況下,一次更新,同一個組件只會render一次。但還有兩種情況,一次更新同一個組件可能render多次:

情況1 并發更新

在并發更新下,存在「低優先級更新進行到中途,被高優先級更新打斷」的情況,這種情況下,同一個組件可能經歷2次更新:

  • 低優先級更新(被打斷)
  • 高優先級更新(沒打斷)

在Demo中render幾千次,顯然不屬于這種情況。

情況2 unwind情況

在React中,有一類組件,在render時是不能確定渲染內容的,比如:

  • Error Boundray
  • Suspense

對于Error Boundray,在render進行到Error Boundray時,React不知道是否應該渲染「報錯對應的UI」,只有繼續遍歷Error Boundray的子孫組件,遇到了報錯,才知道最近的Error Boundray需要渲染成「報錯對應的UI」。

比如,對于下述組件結構:

<ErrorBoundary>
  <A>
    <B/>
  </A>
</ErrorBoundary>

更新進行到ErrorBoundary時,是不知道是否應該渲染「報錯對應的UI」,只有繼續遍歷A、B,報錯以后,才知道ErrorBoundary需要渲染成「報錯對應的UI」。

同理,對于下述組件結構:

<Suspense fallback={<div>加載...</div>}>
  <A>
    <B/>
  </A>
</Suspense>

更新進行到Suspense時,是不知道是否應該渲染「fallback對應的UI」,只有繼續遍歷A、B,發生掛起后,才知道Suspense需要渲染成「fallback對應的UI」。

對于上述兩種情況,React中存在一種「在同一個更新中的回溯,重試機制」,被稱為unwind流程。

在Demo中,就是遭遇了上千次的unwind。

那unwind流程是如何進行的呢?以下述代碼為例:

<ErrorBoundary>
  <A>
    <B/>
  </A>
</ErrorBoundary>

正常更新流程是:

假設B render時拋出錯誤,則會從B往上回到最近的ErrorBoundary:

再重新往下更新:

其中,「從B回到ErrorBoundary」(途中紅色路徑)就是unwind流程。

Demo情況詳解

在Demo中完整的更新流程如下:

首先,首屏渲染遇到第一個React.lazy,開始請求Lazy.tsx的代碼:

更新無法繼續下去(Lazy.tsx代碼還沒請求回),進入unwind流程,回到Suspense:

Suspense再重新往下更新,進入fallback(即<div>外層加載...</div>)的渲染流程:

所以頁面首屏渲染會顯示<div>外層加載...</div>。

當React.lazy請求回Lazy.tsx代碼后,開啟新的更新流程:

當再次遇到React.lazy(請求<div>子組件</div>代碼),又會進入unwind流程。

但是內層的React.lazy與外層的React.lazy是不一樣的,外層的React.lazy是在模塊中定義的:

// App.tsx
const LazyCpn = lazy(() => import("./Lazy"));

內層的React.lazy是在useMemo回調中定義的:

const ChildComponent = useMemo(() => {
  const LazyCpn = lazy(
    () => Promise.resolve({ default: () => <div>子組件</div>})
  )

  return (
    <Suspense fallback={<div>內層加載...</div>}>
      <LazyCpn />
    </Suspense>  
  );
}, []);

前者的引用是穩定的,而后者每次執行useMemo回調都會生成新的引用。

這意味著當unwind進入Suspense,重新往下更新,更新進入到LazyComponent后,useMemo回調執行,創建新的React.lazy,又會進入unwind流程:

在同一個更新中,上圖藍色、紅色流程會循環出現上千次,直到命中邊界情況停止循環。

相對應的,useMemo即使依賴不變,也會在一次更新中執行上千次。

總結

「hook依賴項變化,回調重新執行」是針對不同更新來說的。

在某些會觸發unwind的場景(比如Suspense、Error Boundary)下,一次更新會重復執行很多次。

在這種情況下,即使hook依賴沒變,回調也會重新執行。因為,這是同一次更新的反復執行,而不是執行了不同更新。

參考資料

[1]在線Demo地址:https://codesandbox.io/s/unruffled-nightingale-thzv7z?file=/src/ImportComponent.js。

責任編輯:姜華 來源: 魔術師卡頌
相關推薦

2022-12-13 08:36:42

D-SMARTOracle數據庫

2012-02-01 10:33:59

Java

2022-06-08 08:01:20

useEffect數組函數

2024-06-03 08:32:54

2009-08-19 16:40:35

C#回調

2009-08-21 17:02:20

ASP.NET異步回調

2009-08-12 10:11:18

C# 回調函數

2009-12-22 19:00:08

WCF回調

2011-05-20 17:19:25

回調函數

2023-11-10 16:31:31

2015-10-26 09:25:42

2019-11-05 10:03:08

callback回調函數javascript

2022-04-12 08:30:52

回調函數代碼調試

2011-07-25 14:32:40

Cocoa 框架 函數

2017-11-16 16:15:28

Await開發嵌套

2011-06-15 11:05:14

C語言回調函數

2010-02-04 16:07:39

C++回調函數

2009-11-04 11:32:20

VB.NET回調函數

2009-08-19 17:10:09

C#回調函數

2023-11-01 17:57:56

React應用程序性能
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲日本乱码在线观看 | 日韩精品一区二区三区在线观看 | 国产精品精品视频一区二区三区 | 成人在线观看免费视频 | 国产亚洲成av人片在线观看桃 | 欧美激情视频一区二区三区免费 | 国产亚洲欧美另类一区二区三区 | 国产一区二区自拍 | 精品日韩 | 一区二区三区四区视频 | 欧美日韩中 | 成人不卡 | 婷婷综合五月天 | 欧美成人一级视频 | 天堂视频免费 | 亚洲一区二区三区观看 | 亚洲成人免费网址 | 亚洲色图综合网 | 日本 欧美 三级 高清 视频 | 欧美一区二区三区 | 成人在线免费av | 美女一区 | 日韩在线视频免费观看 | 久热伊人| 精品国产久 | av在线天天| 成人在线免费看 | 国产日韩一区二区三免费高清 | 国产伦精品一区二区三区照片91 | av手机免费在线观看 | 亚洲福利在线观看 | 精品一区二区三区四区五区 | 特级a欧美做爰片毛片 | 欧美中文一区 | 久久精品美女 | 国产精品成人一区二区三区 | 国产精品免费一区二区三区四区 | 欧美三级久久久 | 91久久精品一区二区三区 | 91精品国产综合久久久久久 | 天天色影视综合 |