老早就想寫一篇關於React渲染的文章,這兩天看到一篇比較不錯英文的文章,翻譯一下(主要是谷歌翻譯,手動狗頭),文章底部會附上原文連結。
React 重新渲染的綜合指南。該指南解釋了什麼是重新渲染,什麼是必要的和不必要的重新渲染,什麼情況下會觸發 React 元件重新渲染。
還包括可以防止重新渲染重要的模式和一些導致不必要的重新渲染和效能不佳的反模式。每個模式和反模式都附有圖片指引和工作程式碼範例。
在談論 React 效能時,我們需要關注兩個主要階段:
當 React 需要使用一些新資料更新應用程式時,會發生重新渲染。通常,這是由於使用者與應用程式互動或通過非同步請求或某些訂閱模型傳入的一些外部資料而發生的。
沒有任何非同步資料更新的非互動式應用永遠不會重新渲染,因此不需要關心重新渲染效能優化。
必要的重新渲染- 重新渲染作為更改源的元件,或直接使用新資訊的元件。例如,如果使用者在輸入欄位中鍵入內容,則管理其狀態的元件需要在每次擊鍵時更新自身,即重新渲染。
不必要的重新渲染- 由於錯誤或低效的應用程式架構,應用程式通過不同的重新渲染機制導致元件的重新渲染。例如,如果使用者在輸入欄位中鍵入,並且在每次擊鍵時重新呈現整個頁面,則該頁面已被不必要地重新呈現。
不必要的重新渲染本身不是問題:React 非常快,通常能夠在使用者沒有注意到任何事情的情況下處理它們。
但是,如果重新渲染髮生得太頻繁和/或在非常重的元件上發生,這可能會導致使用者體驗出現「滯後」,每次互動都會出現明顯的延遲,甚至應用程式變得完全沒有響應。
元件自身重新渲染有四個原因:狀態更改、父(或子)重新渲染、上下文更改和hooks更改。還有一個很大的誤區:當元件的props發生變化時會發生重新渲染。就其本身而言,它是不正確的(參見下面的解釋)。
當元件的狀態發生變化時,它會重新渲染自己。通常,它發生在回撥或useEffect
hooks中。
狀態變化是所有重新渲染的「根」源。
如果父元件重新渲染,元件將重新渲染自己。或者,如果我們從相反的方向來看:當一個元件重新渲染時,它也會重新渲染它的所有子元件。
它總是從根向下渲染,子的重新渲染不會觸發父的重新渲染。(這裡有一些警告和邊緣情況,請參閱完整指南瞭解更多詳細資訊:React Element、children、parents 和 re-renders 的奧祕)。
當 Context Provider 中的值發生變化時,所有使用此 Context 的元件都將重新渲染,即使它們不直接使用資料變化的部分。這些重新渲染無法通過直接memo來防止,但是有一些可以模擬的變通方法
參見【第 7 部分:防止由 Context 引起的重新渲染】
hooks內發生的所有事情都「屬於」使用它的元件。關於conext和狀態變化的相同規則適用於此:
hooks可以是鏈式的。鏈中的每個勾點仍然屬於宿主,同樣的規則適用於它們中的任何一個。
當談到沒有被memo包裹的元件重新渲染,元件的props是否改變並不重要。
為了改變 props,它們需要由父元件更新。這意味著父元件必須重新渲染,這將觸發子元件的重新渲染,而不管props是什麼。
只有當使用momo技術(React.memo
, useMemo
)時,props的變化才變得重要。
在另一個元件的渲染函數中建立元件是一種反模式,可能是最大的效能殺手。在每次重新渲染時,React 都會重新安裝這個元件(即銷燬它並從頭開始重新建立它),這將比正常的重新渲染慢得多。最重要的是,這將導致以下錯誤:
需要閱讀的其他資源:如何編寫高效能的 React 程式碼:規則、模式、注意事項
當一個重量級元件管理狀態,並且這個狀態只用於呈現樹的一小部分時,這種模式會很有用。一個典型的例子是在呈現頁面大部分內容的複雜元件中通過單擊按鈕開啟/關閉對話方塊。
在這種情況下,控制模態對話方塊外觀的狀態、對話方塊本身以及觸發更新的按鈕都可以封裝在一個更小的元件中。因此,較大的元件不會在這些狀態更改時重新渲染。
這也可以稱為「包裹狀態作為children」。這種模式類似於「下移狀態」:它將狀態變化封裝在一個較小的元件中。這裡的區別在於狀態用於包裝渲染樹的緩慢部分的元素,因此不能那麼容易地使用它。一個典型的例子是附加到元件根元素的回撥onScroll
。onMouseMove
在這種情況下,可以將狀態管理和使用該狀態的元件提取到一個較小的元件中,並將VerySlowComponent
元件作為children
. 從較小的元件角度來看,子元件只是props,所以它們不會受到狀態變化的影響,因此不會重新渲染。
與之前的模式幾乎相同,具有相同的行為:它將狀態封裝在一個較小的元件中,而重元件作為 props 傳遞給它。道具不受狀態變化的影響,因此重型元件不會重新渲染。
當一些重量級元件獨立於狀態,但不能作為一個組作為子級提取時,它可能很有用。
在React中包裝元件。Memo將停止在渲染樹的下游重新渲染,除非這個元件的props發生了變化。
當渲染一個不依賴於重渲染源(例如,狀態,更改的資料)的重渲染元件時,這是很有用的。
所有不是原始值的props都必須被useMemo,以便 React.memo 工作
React.memo
必須應用於作為children或props傳遞的元素。memo父元件將不工作:子元件和props將是物件,因此它們會隨著每次重新渲染而改變。
記憶的props不會阻止子元件的重新渲染。如果父元件重新渲染,它將觸發子元件的重新渲染,而不管其props如何。
如果一個子元件被React.memo
包裹,所有不是值型別的props都必須被記憶。
useEffect
如果在一個元件中, 之類的hooks中使用非值型別作為依賴項。
則應該使用useMemo
,useCallback
對其進行記憶。
其中一個用例useMemo
是避免每次重新渲染時進行昂貴的計算。
useMemo
有它的成本(消耗一些記憶體並使初始渲染稍微慢一些),所以它不應該用於每次計算。在 React 中,在大多數情況下,安裝和更新元件將是最昂貴的計算(除非您實際上是在計算素數,否則無論如何都不應該在前端進行)。
因此,典型的用例useMemo
是記憶 React 元素。通常是現有渲染樹的一部分或生成的渲染樹的結果,例如返回新元素的對映函數。
與元件更新相比,「純」javascript 操作(如排序或過濾陣列)的成本通常可以忽略不計。
除了常規的重新渲染規則和模式之外,該key
屬性還會影響 React 中列表的效能。
重要提示:僅提供key
屬性不會提高列表的效能。為了防止重新呈現列表元素,您需要將它們包裝起來React.memo
並遵循其所有最佳實踐。
值作為key
應該是一個字串,這在列表中每個元素的重新渲染之間是一致的。通常,使用專案id
或陣列index
。
可以使用陣列index
作為鍵,如果列表是靜態的,即不新增/刪除/插入/重新排序元素。
在動態列表上使用陣列的索引會導致:
在此處閱讀有關此內容的更多詳細資訊:React 關鍵屬性:效能列表的最佳實踐。
隨機生成的值永遠不應用作key
列表中屬性的值。它們將導致 React 在每次重新渲染時重新安裝專案,這將導致:
如果 Context Provider 不是放在應用程式的最根目錄,並且由於其祖先的更改,它可能會重新渲染自身,則應該記住它的值。
如果在 Context 中存在資料和 API(getter 和 setter)的組合,則它們可以拆分為同一元件下的不同 Provider。這樣,使用 API 的元件僅在資料更改時不會重新渲染。
在此處閱讀有關此模式的更多資訊:如何使用 Context 編寫高效能的 React 應用程式
如果 Context 管理一些獨立的資料塊,它們可以被拆分為同一個提供者下的更小的提供者。這樣,只有更改塊的消費者才會重新渲染。
使用部分 Context 值的沒有辦法阻止元件重新渲染,即使使用的資料沒有更改,即使使用useMemo
勾點也是如此。
然而,上下文選擇器可以通過使用高階元件和React.memo
.
在此處閱讀有關此模式的更多資訊:React Hooks 時代的高階元件
已上的內容來自 # React re-renders guide: everything, all at once. 英文好的同學可以直接看原文更佳。
如果你覺得該文章不錯,不妨
1、點贊,讓更多的人也能看到這篇內容
2、關注我,讓我們成為長期關係
3、關注公眾號「前端有話說」,裡面已有多篇原創文章,和開發工具,歡迎各位的關注,第一時間閱讀我的文章