聊聊怎么利用Memoization提高React性能( 二 )


React 中的 Memoization在 React 應用的上下文中,Memoization 是一種手段,每當父組件重新渲染時,子組件僅在它所依賴的 props 發生變化時才會重新渲染 。 如果子組件所依賴的 props 中沒有更改,則它不會執行 render 方法,并將返回緩存的結果 。 由于渲染方法未執行,因此不會有虛擬 DOM 創建和差異檢查,從而實現性能的提升 。
現在,讓我們看看如何在類和函數組件中實現 Memoization,以避免這種不必要的重新渲染 。
類組件實現 Memoization為了在類組件中實現 Memoization,我們將使用 React.PureComponent 。 React.PureComponent 實現了 shouldComponentUpdate(),它對 stateprops 進行了淺比較,并且僅在 props 或 state 發生更改時才重新渲染 React 組件 。
將子組件更改為如下所示的代碼:
//Child.jsclass 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 renderChild renderParent renderParent render對于首次渲染,它同時調用父組件和子組件的 render 方法 。
對于每次增加 count 后的重新渲染,僅調用父組件的 render 函數 。 子組件不會重新渲染 。
函數組件實現 Memoization為了在函數組件中實現 Memoization,我們將使用 React.memo() 。 React.memo() 是一個高階組件(HOC),它執行與 PureComponent 類似的工作,來避免不必要的重新渲染 。
以下是函數組件的代碼:
//Child.jsexport function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> );}export default React.memo(Child); // 這里我們給子組件添加 HOC 實現 Memoization同時還將父組件轉換為了函數組件,如下所示:
【聊聊怎么利用Memoization提高React性能】//Parent.jsexport 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 renderChild renderParent renderParent renderParent renderReact.memo() 存在的問題在上面的示例中,我們看到,當我們對子組件使用 React.memo() HOC 時,子組件沒有重新渲染,即使父組件重新渲染了 。
但是,需要注意的一個小問題是,如果我們將函數作為參數傳遞給子組件,即使在使用 React.memo() 之后,子組件也會重新渲染 。 讓我們看一個這樣的例子 。
我們將更改父組件,如下所示 。 在這里,我們添加了一個處理函數,并作為參數傳遞給子組件:
//Parent.jsexport 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.jsexport 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() 來避免更多的重復渲染導致子組件重新渲染的主要問題是重新創建了

推薦閱讀