在本教學中,我們將學習如何在 React 中實現 Memoization。Memoization 通過快取函數呼叫的結果並在再次需要時返回這些快取的結果來提高效能。
我們將介紹以下內容:
本文假設你對 React 中的類和函陣列件有基本的瞭解。
如果你想查閱這些主題,可以檢視 React 官方檔案 components and props
https://reactjs.org/docs/components-and-props.html
在討論 React 中的 Memoization 細節之前,讓我們先來看看 React 是如何使用虛擬 DOM 渲染 UI 的。【相關推薦:Redis視訊教學】
常規 DOM 基本上包含一組用樹的形式儲存的節點。DOM 中的每個節點代表一個 UI 元素。每當應用程式中出現狀態變更時,該 UI 元素及其所有子元素的相應節點都會在 DOM 樹中更新,然後會觸發 UI 重繪。
在高效的 DOM 樹演演算法的幫助下,更新節點的速度更快,但重繪的速度很慢,並且當該 DOM 具有大量 UI 元素時,可能會影響效能。因此,在 React 中引入了虛擬 DOM。
這是真實 DOM 的虛擬表示。現在,每當應用程式的狀態有任何變化時,React 不會直接更新真正的 DOM,而是建立一個新的虛擬 DOM。然後 React 會將此新的虛擬 DOM 與之前建立的虛擬 DOM 進行比較,找到有差異的地方(譯者注:也就是找到需要被更新節點),然後進行重繪。
根據這些差異,虛擬 DOM 能更高效地更新真正的 DOM。這樣提高了效能,因為虛擬 DOM 不會簡單地更新 UI 元素及其所有子元素,而是有效地僅更新實際 DOM 中必要且最小的更改。
在上一節中,我們看到了 React 如何使用虛擬 DOM 有效地執行 DOM 更新操作來提高效能。在本節中,我們將介紹一個例子,該例子解釋了為了進一步提高效能而需要使用 Memoization。
我們將建立一個父類別,包含一個按鈕,用於遞增名為 count
的變數。父元件還呼叫了子元件,並向其傳遞引數。我們還在 render
方法中新增了 console.log()
語句:
//Parent.js class Parent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } handleClick = () => { this.setState((prevState) => { return { count: prevState.count + 1 }; }); }; render() { console.log("Parent render"); return ( <div className="App"> <button onClick={this.handleClick}>Increment</button> <h2>{this.state.count}</h2> <Child name={"joe"} /> </div> ); } } export default Parent;
此範例的完整程式碼可在 CodeSandbox 上檢視。
我們將建立一個 Child
類,該類接受父元件傳遞的引數並將其顯示在 UI 中:
//Child.js class Child extends React.Component { render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
每當我們點選父元件中的按鈕時,count
值都會更改。由於 state 變化了,因此父元件的 render
方法被執行了。
傳遞給子元件的引數在每次父元件重新渲染時都沒有改變,因此子元件不應重新渲染。然而,當我們執行上面的程式碼並繼續遞增 count
時,我們得到了以下輸出:
Parent render Child render Parent render Child render Parent render Child render
你可以在這個 sandbox 中體驗上述範例,並檢視控制檯的輸出結果。
從輸出中,我們可以看到,當父元件重新渲染時,即使傳遞給子元件的引數保持不變,子元件也會重新渲染。這將導致子元件的虛擬 DOM 與以前的虛擬 DOM 執行差異檢查。由於我們的子元件中沒有變更且重新渲染時的所有 props 都沒有變,所以真正的 DOM 不會被更新。
真正的 DOM 不會進行不必要地更新對效能確實是有好處,但是我們可以看到,即使子元件中沒有實際更改,也會建立新的虛擬 DOM 並執行差異檢查。對於小型 React 元件,這種效能消耗可以忽略不計,但對於大型元件,效能影響會很大。為了避免這種重新渲染和虛擬 DOM 的差異檢查,我們使用 Memoization。
在 React 應用的上下文中,Memoization 是一種手段,每當父元件重新渲染時,子元件僅在它所依賴的 props 發生變化時才會重新渲染。如果子元件所依賴的 props 中沒有更改,則它不會執行 render 方法,並將返回快取的結果。由於渲染方法未執行,因此不會有虛擬 DOM 建立和差異檢查,從而實現效能的提升。
現在,讓我們看看如何在類和函陣列件中實現 Memoization,以避免這種不必要的重新渲染。
為了在類元件中實現 Memoization,我們將使用 React.PureComponent。React.PureComponent
實現了 shouldComponentUpdate(),它對 state
和 props
進行了淺比較,並且僅在 props 或 state 發生更改時才重新渲染 React 元件。
將子元件更改為如下所示的程式碼:
//Child.js class Child extends React.PureComponent { // 這裡我們把 React.Component 改成了 React.PureComponent render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
此範例的完整程式碼顯示在這個 sandbox 中。
父元件保持不變。現在,當我們在父元件中增加 count
時,控制檯中的輸出如下所示:
Parent render Child render Parent render Parent render
對於首次渲染,它同時呼叫父元件和子元件的 render
方法。
對於每次增加 count
後的重新渲染,僅呼叫父元件的 render
函數。子元件不會重新渲染。
為了在函陣列件中實現 Memoization,我們將使用 React.memo()。React.memo()
是一個高階元件(HOC),它執行與 PureComponent
類似的工作,來避免不必要的重新渲染。
以下是函陣列件的程式碼:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> ); } export default React.memo(Child); // 這裡我們給子元件新增 HOC 實現 Memoization
同時還將父元件轉換為了函陣列件,如下所示:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; console.log("Parent render"); return ( <div> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} /> </div> ); }
此範例的完整程式碼可以在這個 sandbox 中看到。
現在,當我們遞增父元件中的 count
時,以下內容將輸出到控制檯:
Parent render Child render Parent render Parent render Parent render
在上面的範例中,我們看到,當我們對子元件使用 React.memo()
HOC 時,子元件沒有重新渲染,即使父元件重新渲染了。
但是,需要注意的一個小問題是,如果我們將函數作為引數傳遞給子元件,即使在使用 React.memo()
之後,子元件也會重新渲染。讓我們看一個這樣的例子。
我們將更改父元件,如下所示。在這裡,我們新增了一個處理常式,並作為引數傳遞給子元件:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = () => { console.log("handler"); // 這裡的 handler 函數將會被傳遞給子元件 }; console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} childFunc={handler} /> </div> ); }
子元件程式碼將保持原樣。我們不會在子元件中使用父元件傳遞來的函數:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> ); } export default React.memo(Child);
現在,當我們遞增父元件中的 count
時,它會重新渲染並同時重新渲染子元件,即使傳遞的引數中沒有更改。
那麼,是什麼原因導致子元件重新渲染的呢?答案是,每次父元件重新渲染時,都會建立一個新的 handler
函數並將其傳遞給子元件。現在,由於每次重新渲染時都會重新建立 handle
函數,因此子元件在對 props 進行淺比較時會發現 handler
參照已更改,並重新渲染子元件。
接下來,我們將介紹如何解決此問題。
useCallback()
來避免更多的重複渲染導致子元件重新渲染的主要問題是重新建立了 handler
函數,這更改了傳遞給子元件的參照。因此,我們需要有一種方法來避免這種重複建立。如果未重新建立 handler
函數,則對 handler
函數的參照不會更改,因此子元件不會重新渲染。
為了避免每次渲染父元件時都重新建立函數,我們將使用一個名為 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有關 Hooks 的更多資訊,你可以檢視 React 的官方 hooks 檔案,或者檢視 `React Hooks: How to Get Started & Build Your Own"。
useCallback()
勾點傳入兩個引數:回撥函數和依賴項列表。
以下是 useCallback()
範例:
const handleClick = useCallback(() => { //Do something }, [x,y]);
在這裡,useCallback()
被新增到 handleClick()
函數中。第二個引數 [x, y]
可以是空陣列、單個依賴項或依賴項列表。每當第二個引數中提到的任何依賴項發生更改時,才會重新建立 handleClick()
函數。
如果 useCallback()
中提到的依賴項沒有更改,則返回作為第一個引數提及的回撥函數的 Memoization 版本。我們將更改父元件,以便對傳遞給子元件的處理程式使用 useCallback()
勾點:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = useCallback(() => { // 給 handler 函數使用 useCallback() console.log("handler"); }, []); console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} childFunc={handler} /> </div> ); }
子元件程式碼將保持原樣。
此範例的完整程式碼這個 sandbox 中。
當我們在上述程式碼的父元件中增加 count
時,我們可以看到以下輸出:
Parent render Child render Parent render Parent render Parent render
由於我們對父元件中的 handler
使用了 useCallback()
勾點,因此每次父元件重新渲染時,都不會重新建立 handler
函數,並且會將 handler
的 Memoization 版本傳遞到子元件。子元件將進行淺比較,並注意到 handler
函數的參照沒有更改,因此它不會呼叫 render
方法。
Memoization 是一種很好的手段,可以避免在元件的 state 或 props 沒有改變時對元件進行不必要的重新渲染,從而提高 React 應用的效能。你可能會考慮為所有元件新增 Memoization,但這並不一定是構建高效能 React 元件的方法。只有在元件出現以下情況時,才應使用 Memoization:
在本教學中,我們理解了:
React.memo()
和類元件的 React.PureComponent
實現 MemoizationReact.memo()
之後,子元件也會重新渲染useCallback()
勾點來避免在函數作為 props 傳遞給子元件時產生重新渲染的問題希望這篇 React Memoization 的介紹對你有幫助!
原文地址:https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/
原文作者:Nida Khan
更多程式設計相關知識,請存取:!!
以上就是聊聊怎麼利用Memoization提高React效能的詳細內容,更多請關注TW511.COM其它相關文章!