您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“如何利用Memoization提高React性能”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
在討論 React 中的 Memoization 細(xì)節(jié)之前,讓我們先來看看 React 是如何使用虛擬 DOM 渲染 UI 的。【相關(guān)推薦:Redis視頻教程】
常規(guī) DOM 基本上包含一組用樹的形式保存的節(jié)點(diǎn)。DOM 中的每個(gè)節(jié)點(diǎn)代表一個(gè) UI 元素。每當(dāng)應(yīng)用程序中出現(xiàn)狀態(tài)變更時(shí),該 UI 元素及其所有子元素的相應(yīng)節(jié)點(diǎn)都會(huì)在 DOM 樹中更新,然后會(huì)觸發(fā) UI 重繪。
在高效的 DOM 樹算法的幫助下,更新節(jié)點(diǎn)的速度更快,但重繪的速度很慢,并且當(dāng)該 DOM 具有大量 UI 元素時(shí),可能會(huì)影響性能。因此,在 React 中引入了虛擬 DOM。
這是真實(shí) DOM 的虛擬表示?,F(xiàn)在,每當(dāng)應(yīng)用程序的狀態(tài)有任何變化時(shí),React 不會(huì)直接更新真正的 DOM,而是創(chuàng)建一個(gè)新的虛擬 DOM。然后 React 會(huì)將此新的虛擬 DOM 與之前創(chuàng)建的虛擬 DOM 進(jìn)行比較,找到有差異的地方(譯者注:也就是找到需要被更新節(jié)點(diǎn)),然后進(jìn)行重繪。
根據(jù)這些差異,虛擬 DOM 能更高效地更新真正的 DOM。這樣提高了性能,因?yàn)樘摂M DOM 不會(huì)簡(jiǎn)單地更新 UI 元素及其所有子元素,而是有效地僅更新實(shí)際 DOM 中必要且最小的更改。
在上一節(jié)中,我們看到了 React 如何使用虛擬 DOM 有效地執(zhí)行 DOM 更新操作來提高性能。在本節(jié)中,我們將介紹一個(gè)例子,該例子解釋了為了進(jìn)一步提高性能而需要使用 Memoization。
我們將創(chuàng)建一個(gè)父類,包含一個(gè)按鈕,用于遞增名為 count
的變量。父組件還調(diào)用了子組件,并向其傳遞參數(shù)。我們還在 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> <h3>{this.state.count}</h3> <Child name={"joe"} /> </div> ); } } export default Parent;
此示例的完整代碼可在 CodeSandbox 上查看。
我們將創(chuàng)建一個(gè) Child
類,該類接受父組件傳遞的參數(shù)并將其顯示在 UI 中:
//Child.js class Child extends React.Component { render() { console.log("Child render"); return ( <div> <h3>{this.props.name}</h3> </div> ); } } export default Child;
每當(dāng)我們點(diǎn)擊父組件中的按鈕時(shí),count
值都會(huì)更改。由于 state 變化了,因此父組件的 render
方法被執(zhí)行了。
傳遞給子組件的參數(shù)在每次父組件重新渲染時(shí)都沒有改變,因此子組件不應(yīng)重新渲染。然而,當(dāng)我們運(yùn)行上面的代碼并繼續(xù)遞增 count
時(shí),我們得到了以下輸出:
Parent render Child render Parent render Child render Parent render Child render
你可以在這個(gè) sandbox 中體驗(yàn)上述示例,并查看控制臺(tái)的輸出結(jié)果。
從輸出中,我們可以看到,當(dāng)父組件重新渲染時(shí),即使傳遞給子組件的參數(shù)保持不變,子組件也會(huì)重新渲染。這將導(dǎo)致子組件的虛擬 DOM 與以前的虛擬 DOM 執(zhí)行差異檢查。由于我們的子組件中沒有變更且重新渲染時(shí)的所有 props 都沒有變,所以真正的 DOM 不會(huì)被更新。
真正的 DOM 不會(huì)進(jìn)行不必要地更新對(duì)性能確實(shí)是有好處,但是我們可以看到,即使子組件中沒有實(shí)際更改,也會(huì)創(chuàng)建新的虛擬 DOM 并執(zhí)行差異檢查。對(duì)于小型 React 組件,這種性能消耗可以忽略不計(jì),但對(duì)于大型組件,性能影響會(huì)很大。為了避免這種重新渲染和虛擬 DOM 的差異檢查,我們使用 Memoization。
在 React 應(yīng)用的上下文中,Memoization 是一種手段,每當(dāng)父組件重新渲染時(shí),子組件僅在它所依賴的 props 發(fā)生變化時(shí)才會(huì)重新渲染。如果子組件所依賴的 props 中沒有更改,則它不會(huì)執(zhí)行 render 方法,并將返回緩存的結(jié)果。由于渲染方法未執(zhí)行,因此不會(huì)有虛擬 DOM 創(chuàng)建和差異檢查,從而實(shí)現(xiàn)性能的提升。
現(xiàn)在,讓我們看看如何在類和函數(shù)組件中實(shí)現(xiàn) Memoization,以避免這種不必要的重新渲染。
為了在類組件中實(shí)現(xiàn) Memoization,我們將使用 React.PureComponent。React.PureComponent
實(shí)現(xiàn)了 shouldComponentUpdate(),它對(duì) state
和 props
進(jìn)行了淺比較,并且僅在 props 或 state 發(fā)生更改時(shí)才重新渲染 React 組件。
將子組件更改為如下所示的代碼:
//Child.js class Child extends React.PureComponent { // 這里我們把 React.Component 改成了 React.PureComponent render() { console.log("Child render"); return ( <div> <h3>{this.props.name}</h3> </div> ); } } export default Child;
此示例的完整代碼顯示在這個(gè) sandbox 中。
父組件保持不變?,F(xiàn)在,當(dāng)我們?cè)诟附M件中增加 count
時(shí),控制臺(tái)中的輸出如下所示:
Parent render Child render Parent render Parent render
對(duì)于首次渲染,它同時(shí)調(diào)用父組件和子組件的 render
方法。
對(duì)于每次增加 count
后的重新渲染,僅調(diào)用父組件的 render
函數(shù)。子組件不會(huì)重新渲染。
為了在函數(shù)組件中實(shí)現(xiàn) Memoization,我們將使用 React.memo()。React.memo()
是一個(gè)高階組件(HOC),它執(zhí)行與 PureComponent
類似的工作,來避免不必要的重新渲染。
以下是函數(shù)組件的代碼:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h3>{props.name}</h3> </div> ); } export default React.memo(Child); // 這里我們給子組件添加 HOC 實(shí)現(xiàn) Memoization
同時(shí)還將父組件轉(zhuǎn)換為了函數(shù)組件,如下所示:
//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> <h3>{count}</h3> <Child name={"joe"} /> </div> ); }
此示例的完整代碼可以在這個(gè) sandbox 中看到。
現(xiàn)在,當(dāng)我們遞增父組件中的 count
時(shí),以下內(nèi)容將輸出到控制臺(tái):
Parent render Child render Parent render Parent render Parent render
在上面的示例中,我們看到,當(dāng)我們對(duì)子組件使用 React.memo()
HOC 時(shí),子組件沒有重新渲染,即使父組件重新渲染了。
但是,需要注意的一個(gè)小問題是,如果我們將函數(shù)作為參數(shù)傳遞給子組件,即使在使用 React.memo()
之后,子組件也會(huì)重新渲染。讓我們看一個(gè)這樣的例子。
我們將更改父組件,如下所示。在這里,我們添加了一個(gè)處理函數(shù),并作為參數(shù)傳遞給子組件:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = () => { console.log("handler"); // 這里的 handler 函數(shù)將會(huì)被傳遞給子組件 }; console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h3>{count}</h3> <Child name={"joe"} childFunc={handler} /> </div> ); }
子組件代碼將保持原樣。我們不會(huì)在子組件中使用父組件傳遞來的函數(shù):
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h3>{props.name}</h3> </div> ); } export default React.memo(Child);
現(xiàn)在,當(dāng)我們遞增父組件中的 count
時(shí),它會(huì)重新渲染并同時(shí)重新渲染子組件,即使傳遞的參數(shù)中沒有更改。
那么,是什么原因?qū)е伦咏M件重新渲染的呢?答案是,每次父組件重新渲染時(shí),都會(huì)創(chuàng)建一個(gè)新的 handler
函數(shù)并將其傳遞給子組件?,F(xiàn)在,由于每次重新渲染時(shí)都會(huì)重新創(chuàng)建 handle
函數(shù),因此子組件在對(duì) props 進(jìn)行淺比較時(shí)會(huì)發(fā)現(xiàn) handler
引用已更改,并重新渲染子組件。
接下來,我們將介紹如何解決此問題。
useCallback()
來避免更多的重復(fù)渲染導(dǎo)致子組件重新渲染的主要問題是重新創(chuàng)建了 handler
函數(shù),這更改了傳遞給子組件的引用。因此,我們需要有一種方法來避免這種重復(fù)創(chuàng)建。如果未重新創(chuàng)建 handler
函數(shù),則對(duì) handler
函數(shù)的引用不會(huì)更改,因此子組件不會(huì)重新渲染。
為了避免每次渲染父組件時(shí)都重新創(chuàng)建函數(shù),我們將使用一個(gè)名為 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有關(guān) Hooks 的更多信息,你可以查看 React 的官方 hooks 文檔,或者查看 `React Hooks: How to Get Started & Build Your Own"。
useCallback()
鉤子傳入兩個(gè)參數(shù):回調(diào)函數(shù)和依賴項(xiàng)列表。
以下是 useCallback()
示例:
const handleClick = useCallback(() => { //Do something }, [x,y]);
在這里,useCallback()
被添加到 handleClick()
函數(shù)中。第二個(gè)參數(shù) [x, y]
可以是空數(shù)組、單個(gè)依賴項(xiàng)或依賴項(xiàng)列表。每當(dāng)?shù)诙€(gè)參數(shù)中提到的任何依賴項(xiàng)發(fā)生更改時(shí),才會(huì)重新創(chuàng)建 handleClick()
函數(shù)。
如果 useCallback()
中提到的依賴項(xiàng)沒有更改,則返回作為第一個(gè)參數(shù)提及的回調(diào)函數(shù)的 Memoization 版本。我們將更改父組件,以便對(duì)傳遞給子組件的處理程序使用 useCallback()
鉤子:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = useCallback(() => { // 給 handler 函數(shù)使用 useCallback() console.log("handler"); }, []); console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h3>{count}</h3> <Child name={"joe"} childFunc={handler} /> </div> ); }
子組件代碼將保持原樣。
此示例的完整代碼這個(gè) sandbox 中。
當(dāng)我們?cè)谏鲜龃a的父組件中增加 count
時(shí),我們可以看到以下輸出:
Parent render Child render Parent render Parent render Parent render
由于我們對(duì)父組件中的 handler
使用了 useCallback()
鉤子,因此每次父組件重新渲染時(shí),都不會(huì)重新創(chuàng)建 handler
函數(shù),并且會(huì)將 handler
的 Memoization 版本傳遞到子組件。子組件將進(jìn)行淺比較,并注意到 handler
函數(shù)的引用沒有更改,因此它不會(huì)調(diào)用 render
方法。
Memoization 是一種很好的手段,可以避免在組件的 state 或 props 沒有改變時(shí)對(duì)組件進(jìn)行不必要的重新渲染,從而提高 React 應(yīng)用的性能。你可能會(huì)考慮為所有組件添加 Memoization,但這并不一定是構(gòu)建高性能 React 組件的方法。只有在組件出現(xiàn)以下情況時(shí),才應(yīng)使用 Memoization:
固定的輸入有固定的輸出時(shí)
具有較多 UI 元素,虛擬 DOM 檢查將影響性能
多次傳遞相同的參數(shù)
“如何利用Memoization提高React性能”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。