React重新渲染指南

前言

老早就想寫一篇關於React渲染的文章,這兩天看到一篇比較不錯英文的文章,翻譯一下(主要是谷歌翻譯,手動狗頭),文章底部會附上原文鏈接。

介紹

React 重新渲染的綜合指南。該指南解釋了什麼是重新渲染,什麼是必要的和不必要的重新渲染,什麼情況下會觸發 React 組件重新渲染。

還包括可以防止重新渲染重要的模式和一些導致不必要的重新渲染和性能不佳的反模式。每個模式和反模式都附有圖片指引和工作代碼示例。

內容

React 的重新渲染是什麼?

在談論 React 性能時,我們需要關注兩個主要階段:

  • 初始渲染- 當組件首次出現在屏幕上時發生
  • 重新渲染- 已經在屏幕上的組件的第二次和任何連續渲染

當 React 需要使用一些新數據更新應用程序時,會發生重新渲染。通常,這是由於用戶與應用程序交互或通過異步請求或某些訂閱模型傳入的一些外部數據而發生的。

沒有任何異步數據更新的非交互式應用永遠不會重新渲染,因此不需要關心重新渲染性能優化。

必要和不必要的重新渲染是什麼?

必要的重新渲染- 重新渲染作爲更改源的組件,或直接使用新信息的組件。例如,如果用戶在輸入字段中鍵入內容,則管理其狀態的組件需要在每次擊鍵時更新自身,即重新渲染。

不必要的重新渲染- 由於錯誤或低效的應用程序架構,應用程序通過不同的重新渲染機制導致組件的重新渲染。例如,如果用戶在輸入字段中鍵入,並且在每次擊鍵時重新呈現整個頁面,則該頁面已被不必要地重新呈現。

不必要的重新渲染本身不是問題:React 非常快,通常能夠在用戶沒有注意到任何事情的情況下處理它們。

但是,如果重新渲染髮生得太頻繁和/或在非常重的組件上發生,這可能會導致用戶體驗出現“滯後”,每次交互都會出現明顯的延遲,甚至應用程序變得完全沒有響應。

React 組件什麼時候會重新渲染自己?

組件自身重新渲染有四個原因:狀態更改、父(或子)重新渲染、上下文更改和hooks更改。還有一個很大的誤區:當組件的props發生變化時會發生重新渲染。就其本身而言,它是不正確的(參見下面的解釋)。

重新渲染的原因:狀態變化

當組件的狀態發生變化時,它會重新渲染自己。通常,它發生在回調或useEffecthooks中。

狀態變化是所有重新渲染的“根”源。

image.png

重新渲染的原因:父級重新渲染

如果父組件重新渲染,組件將重新渲染自己。或者,如果我們從相反的方向來看:當一個組件重新渲染時,它也會重新渲染它的所有子組件。

它總是從根向下渲染,子的重新渲染不會觸發父的重新渲染。(這裏有一些警告和邊緣情況,請參閱完整指南瞭解更多詳細信息:React Element、children、parents 和 re-renders 的奧祕)。

image.png

重新渲染的原因:context 變化

當 Context Provider 中的值發生變化時,所有使用此 Context 的組件都將重新渲染,即使它們不直接使用數據變化的部分。這些重新渲染無法通過直接memo來防止,但是有一些可以模擬的變通方法

參見【第 7 部分:防止由 Context 引起的重新渲染】

image.png

重新渲染的原因:hooks變化

hooks內發生的所有事情都“屬於”使用它的組件。關於conext和狀態變化的相同規則適用於此:

  • hooks內的狀態更改將觸發不可避免的宿主重複渲染
  • 如果hooks使用了 Context 並且 Context 的值發生了變化,它會觸發一個不可避免的重複渲染

hooks可以是鏈式的。鏈中的每個鉤子仍然屬於宿主,同樣的規則適用於它們中的任何一個。

image.png

重新渲染的原因:props的變化(很大的一個誤區)

當談到沒有被memo包裹的組件重新渲染,組件的props是否改變並不重要。

爲了改變 props,它們需要由父組件更新。這意味着父組件必須重新渲染,這將觸發子組件的重新渲染,而不管props是什麼。

只有當使用momo技術(React.memouseMemo)時,props的變化才變得重要。

image.png

防止合成重複渲染?

反模式:在渲染函數中創建組件

在另一個組件的渲染函數中創建組件是一種反模式,可能是最大的性能殺手。在每次重新渲染時,React 都會重新安裝這個組件(即銷燬它並從頭開始重新創建它),這將比正常的重新渲染慢得多。最重要的是,這將導致以下錯誤:

  • 重新渲染期間可能出現內容“閃爍”
  • 每次重新渲染時都會在組件中重置狀態
  • useEffect 每次重新渲染都不會觸發依賴項
  • 如果一個組件被聚焦,焦點將丟失

需要閱讀的其他資源:如何編寫高性能的 React 代碼:規則、模式、注意事項

image.png

向下移動狀態

當一個重量級組件管理狀態,並且這個狀態只用於呈現樹的一小部分時,這種模式會很有用。一個典型的例子是在呈現頁面大部分內容的複雜組件中通過單擊按鈕打開/關閉對話框。

在這種情況下,控制模態對話框外觀的狀態、對話框本身以及觸發更新的按鈕都可以封裝在一個更小的組件中。因此,較大的組件不會在這些狀態更改時重新渲染。

image.png

children 作爲 props

這也可以稱爲“包裹狀態作爲children”。這種模式類似於“下移狀態”:它將狀態變化封裝在一個較小的組件中。這裏的區別在於狀態用於包裝渲染樹的緩慢部分的元素,因此不能那麼容易地使用它。一個典型的例子是附加到組件根元素的回調onScrollonMouseMove

在這種情況下,可以將狀態管理和使用該狀態的組件提取到一個較小的組件中,並將VerySlowComponent組件作爲children. 從較小的組件角度來看,子組件只是props,所以它們不會受到狀態變化的影響,因此不會重新渲染。

image.png

組件作爲props

與之前的模式幾乎相同,具有相同的行爲:它將狀態封裝在一個較小的組件中,而重組件作爲 props 傳遞給它。道具不受狀態變化的影響,因此重型組件不會重新渲染。

當一些重量級組件獨立於狀態,但不能作爲一個組作爲子級提取時,它可能很有用。

image.png

使用 React.memo 防止重新渲染

在React中包裝組件。Memo將停止在渲染樹的下游重新渲染,除非這個組件的props發生了變化。

當渲染一個不依賴於重渲染源(例如,狀態,更改的數據)的重渲染組件時,這是很有用的。

image.png

組件有 props

所有不是原始值的props都必須被useMemo,以便 React.memo 工作

image.png

組件作爲props或children

React.memo必須應用於作爲children或props傳遞的元素。memo父組件將不工作:子組件和props將是對象,因此它們會隨着每次重新渲染而改變。

image.png

使用 useMemo/useCallback 提高重新渲染性能

反模式:props 上不必要的 useMemo/useCallback

記憶的props不會阻止子組件的重新渲染。如果父組件重新渲染,它將觸發子組件的重新渲染,而不管其props如何。

image.png

必要的 useMemo/useCallback

如果一個子組件被React.memo包裹,所有不是值類型的props都必須被記憶。

image.png

useEffect如果在一個組件中, 之類的hooks中使用非值類型作爲依賴項。
則應該使用useMemouseCallback對其進行記憶。

image.png

使用Memo 進行昂貴的計算

其中一個用例useMemo是避免每次重新渲染時進行昂貴的計算。

useMemo有它的成本(消耗一些內存並使初始渲染稍微慢一些),所以它不應該用於每次計算。在 React 中,在大多數情況下,安裝和更新組件將是最昂貴的計算(除非您實際上是在計算素數,否則無論如何都不應該在前端進行)。

因此,典型的用例useMemo是記憶 React 元素。通常是現有渲染樹的一部分或生成的渲染樹的結果,例如返回新元素的映射函數。

與組件更新相比,“純”javascript 操作(如排序或過濾數組)的成本通常可以忽略不計。

image.png

提高列表的重新渲染性能

除了常規的重新渲染規則和模式之外,該key屬性還會影響 React 中列表的性能。

重要提示:僅提供key屬性不會提高列表的性能。爲了防止重新呈現列表元素,您需要將它們包裝起來React.memo並遵循其所有最佳實踐。

值作爲key應該是一個字符串,這在列表中每個元素的重新渲染之間是一致的。通常,使用項目id 或數組index

可以使用數組index作爲鍵,如果列表是靜態的,即不添加/刪除/插入/重新排序元素。

在動態列表上使用數組的索引會導致:

  • 如果項目具有狀態或任何不受控制的元素(如表單輸入),則會出現錯誤
  • 如果項目包裝在 React.memo 中,性能會下降

在此處閱讀有關此內容的更多詳細信息:React 關鍵屬性:性能列表的最佳實踐

image.png

反模式:隨機值作爲列表中的鍵

隨機生成的值永遠不應用作key列表中屬性的值。它們將導致 React 在每次重新渲染時重新安裝項目,這將導致:

  • 列表的表現很差
  • 如果項目具有狀態或任何不受控制的元素(如表單輸入),則會出現錯誤

image.png

防止由Context引起的重新渲染

記憶 Provider 值

如果 Context Provider 不是放在應用程序的最根目錄,並且由於其祖先的更改,它可能會重新渲染自身,則應該記住它的值。

image.png

拆分數據和 API

如果在 Context 中存在數據和 API(getter 和 setter)的組合,則它們可以拆分爲同一組件下的不同 Provider。這樣,使用 API 的組件僅在數據更改時不會重新渲染。

在此處閱讀有關此模式的更多信息:如何使用 Context 編寫高性能的 React 應用程序

image.png

將數據分成塊

如果 Context 管理一些獨立的數據塊,它們可以被拆分爲同一個提供者下的更小的提供者。這樣,只有更改塊的消費者纔會重新渲染。

image.png

Context selectors (上下文選擇器)

使用部分 Context 值的沒有辦法阻止組件重新渲染,即使使用的數據沒有更改,即使使用useMemo鉤子也是如此。

然而,上下文選擇器可以通過使用高階組件和React.memo.

在此處閱讀有關此模式的更多信息:React Hooks 時代的高階組件

image.png

結束語

已上的內容來自 # React re-renders guide: everything, all at once. 英文好的同學可以直接看原文更佳。

如果你覺得該文章不錯,不妨

1、點贊,讓更多的人也能看到這篇內容

2、關注我,讓我們成爲長期關係

3、關注公衆號「前端有話說」,裏面已有多篇原創文章,和開發工具,歡迎各位的關注,第一時間閱讀我的文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章