七大提高React 性能的技巧
介紹
一些剛開始學習 React,或者從其他框架轉入 React 的開發者,一開始可能不會太關注性能。因為需要一些時間來發現新學習的框架的性能缺點。
后來,由于缺乏經驗,這些開發人員在編寫代碼時會犯一些小錯誤,最終會累積起來并導致性能下降。此外,他們將很難解決問題。
在這里,我們將探討 7 個技巧,這些技巧將有助于避免構建任何類型的應用程序時出現的大多數 React 性能問題。
1. 解決重復渲染問題
我們大多數人都知道虛擬 DOM 是如何工作的,但最重要的是檢測何時觸發樹比較。當我們可以跟蹤它時,我們可以控制組件的重新渲染,并最終防止意外的性能流。令人驚訝的是,它并不難捕捉。首先,將 React Devtool 擴展添加到瀏覽器。
- 然后打開瀏覽器開發者工具(在 Chrome 中是 Option + ? + J(在 macOS 上),或 Shift + CTRL + J(在 Windows/Linux 上)。
- 選擇組件
- 點擊設置圖標
- 并選中“組件渲染時突出顯示更新”
就是這樣,現在,當我們與 UI 交互時,它會在當前重新渲染的元素上顯示綠色邊框。知道了這一點,我們就可以分析我們的任何 React 組件并重構它們以避免不必要的重新渲染。
2.通過拆分組件減少重新渲染
如果我們能夠減少意外重新渲染元素的數量,它將解決 React 中的大部分性能問題。
但我們必須首先回答這個問題:“是什么觸發了重新渲染?”。答案很簡單,狀態改變了。
每次組件狀態發生變化時,它都會喚醒樹比較,也稱為協調,并重新呈現狀態上下文的元素。
狀態上下文,是初始化此類狀態的組件。意思是,如果我們有一個巨大的組件,它有很多狀態(不需要相互依賴)并且其中一個狀態發生了變化,它將重新渲染整個組件元素,這絕對不是我們想要的。
那么,解決方案是什么?解決方法是通過將組件的一部分和它的一些狀態移動到它自己的子組件中來分離狀態上下文,現在,讓我們看一下這個例子:
假設我們有一個帶有搜索過濾器的表格組件。搜索過濾器是一個受控輸入,其狀態在輸入文本更改后更新。這是它的樣子:
當我們開始在搜索輸入字段中輸入時會發生什么?
是的,它將重新呈現整個表格元素。發生這種情況是因為輸入狀態上下文與表組件共享相同的上下文。
現在,讓我們嘗試我們的解決方案,將輸入元素及其狀態移動到一個單獨的組件中,并將其注入到表格組件中。
神奇的事情發生了,表格組件不再重新渲染。我們稍后可以通過從輸入發出事件來控制我們希望輸入影響表格元素的確切時間來增強功能。
好的做法是拆分組件以分離狀態上下文,以避免冗余的重新渲染。
3. 什么是實例重創建,如何避免?
我們已經發現狀態更改會觸發組件重新渲染,但是我們需要考慮另一個重要的副作用。
當狀態改變和協調發生時,它將重新初始化整個組件實例并保持新的狀態值。這對我們來說意味著,在協調期間,將重新創建所有函數實例,以便能夠考慮新的狀態值,我們不需要它,在大多數情況下,函數可以只依賴于幾個狀態,我們不想重新創建不依賴于已更改狀態的函數實例。
這是一個提高性能的機會,我們有幾個解決方案:useCallback 和 useRef。讓我們看個例子:
這是最常見的例子。我們有依賴于狀態 someState 的 foo。當 someState 改變時,它將重新創建 foo 的新實例。
這段代碼的問題是,即使其他一些狀態發生變化,比如 otherState,foo 也會被重新創建,這是我們實際上不想要的。我們可以使用 useCallback 來告訴 React 我們的函數狀態依賴是什么,以便更明確地說明何時重新創建實例:
在此示例中,我們將依賴項數組傳遞給 useCallback 掛鉤。更好的是,foo 將避免其他狀態更改。
另一種選擇是使用 useRef。useRef——你可以把它想象成和 useState 一樣,但不會觸發組件重新渲染(UI 不會更新)。useRef 沒有依賴列表,所以我們需要傳遞 someState as foo 屬性:
在這種情況下,我們根本不會重新創建 foo 實例。
結論:使用 useCallback 和 useRef 來控制函數實例的重新創建。
4. 不要偷懶懶加載
React 默認同步渲染組件。這意味著組件將等到其子項被渲染后再渲染自己。沒有必要等待,尤其是當一些子組件沒有耦合時。它可能會導致頁面掛起。
假設我們點擊了一些導航鏈接,假設將我們重定向到另一個頁面。導航將等待所有頁面組件呈現完成重定向。它會影響用戶體驗,人們不會等待,只會離開您的網站。
我們需要使頁面內容異步呈現,以免損害導航。解決方案是將您的頁面組件包裝到 React.lazy(() 并告訴 React 完成導航,然后等待頁面組件完成渲染:
稍后我們可以使用 <Suspense/> 在頁面組件尚未準備好時,顯示一些加載動畫。
這并不意味著我們必須在任何地方都使用 Lazy load 組件,當我們在不會對性能造成太大損害的地方使用它時,它可能會導致過度工程。
另一種場景是一些組件可能默認隱藏在 UI 中,所以我們不必等待它們。例如模態窗口、對話框、抽屜和可折疊的側面板。
延遲加載頁面組件和隱藏的 UI 組件。
5. 何時使用 React 片段?
它經常發生,當我們在 JSX 中構建一些布局并想要對我們的元素進行分組時,在大多數情況下我們使用 <div> 標簽。或者,例如,我們有我們想要移動到單獨組件中的父子 HTML 標記:
因此,當我們將 <li> 移動到單獨的組件中時,例如:
并改變它:
渲染后,它看起來像這樣:
這將創建一個我們不需要的額外 <div> 節點。
這將使我們的 DOM 樹更加嵌套,從而減慢協調過程。
相反, 我們可以將我們的<div> 子元素包裝到 Fragment 中。
最初,Fragment 允許您對 DOM 元素進行分組,插入后只會導致一次重排。
在 React 中,Fragment 也會讓你減少不必要的節點。當你想對元素進行分組時,你唯一需要做的就是使用 Fragment 而不是 <div> :
就是這樣,就這么簡單。
如果要對元素進行分組以減少節點數,請使用 Fragment。
6.避免在列出的元素中使用索引作為鍵
大家都知道,如果沒有,Eslint 會強制執行在列出的元素中使用鍵,例如:
React 中的關鍵是唯一標識符,它幫助 React 指向列表中的正確元素并更新正確的元素。如果我們使用索引作為列表中的鍵,比如:
我們將元素映射到它的索引。但是如果我們有排序,列表中元素的順序可能會改變,初始鍵將不再指向正確的元素。
始終使用唯一 id 作為列出元素的鍵,如果對象沒有它,您可以使用外部庫顯式分配,如 uid。
7.避免Spread Props
這是今天的最后一個修改調整技巧,已經很多了, 你一定見過,甚至自己親手做過spreading props。就像是:
它不僅迫使您猜測實際輸入接收到的屬性是什么,而且還會在輸入元素中創建一堆您不一定需要的屬性。
讓它明確,并且不要害怕根據需要傳遞盡可能多的屬性,您總是可以將它們分組到某個對象中:
很好,現在更具可讀性。
永遠不要spread props,分別傳遞每個屬性。
總結
我想,您可能已經知道 Eslint 強制執行的一些調整,但是現在您知道為什么遵循它們很重要了,而且,您可以對代碼進行性能分析,這將為您提供改進空間。