現象
React 中,通常父組件的某個state發生改變,會引起父組件的重新渲染(和其他state的重新計算),從而會導致子組件的重新渲染(和其他非相關屬性的重新計算)
- 問題一:如何避免因為某個state變化,導致組件的中其他屬性(state)的重新計算?
? ? ? ?方案:useMemo
- 問題二:如何避免因為父組件的重新渲染,導致子組件中非相關屬性的重新計算?
? ? ? ?方案:React.memo
結論
總結起來就一句話:
React.memo 用來限制組件重新渲染,而 useMemo?用來限制組件中的部分變量重新計算
(前者主要針對組件的渲染,后者則側重于組件內的計算)
示例
React.memo:【常用于子組件】
它是一個高階組件(Higher-Order Component,HOC),用于包裝函數組件。
當父組件重新渲染時,往往會觸發子組件的重新渲染。但很多時候子組件的?props
?并沒有改變,此時子組件的重新渲染就是不必要的,會造成性能浪費。
經過?React.memo
?包裝的組件,React 會對其?props
?進行淺比較,如果新?props
?和舊?props
?相同,組件不會重新渲染,而是復用之前的渲染結果。(淺比較只會檢查對象或數組的引用是否相同,而不會深入比較其內部的屬性或元素)
import React from 'react';// 使用 React.memo 包裹組件
const MyComponent = React.memo(({ data }) => {console.log('組件重新渲染');return <div>{data}</div>;
});const ParentComponent = () => {const [count, setCount] = React.useState(0);const someData = '固定數據';return (<div>{/* 如果子組件不用React.memo包裹,count變化后子組件也會重新渲染 */}<button onClick={() => setCount(count + 1)}>增加計數</button>{/* 只要 someData 不變,MyComponent 不會重新渲染 */}<MyComponent data={someData} /></div>);
};export default ParentComponent;
useMemo:
這是一個 React Hook,只能用于函數組件內部。
它主要用于緩存計算結果,根據傳入的依賴項數組來判斷是否需要重新計算緩存的值,避免在每次組件渲染時都進行重復的高開銷計算。
如果計算屬性作為子組件的?props
?傳遞,且子組件使用?React.memo
?進行了優化,在父組件使用?useMemo
?可以確保計算屬性的引用在依賴項不變時保持穩定,從而避免子組件不必要的重新渲染。
import React, { useState, useMemo, memo } from 'react';const ChildComponent = memo(({ data }) => {console.log('ChildComponent rendered');return <div>{data}</div>;
});const ParentComponent = () => {const [num, setNum] = useState(1);const calculatedData = useMemo(() => {return num * 2;}, [num]);return (<div><inputtype="number"value={num}onChange={(e) => setNum(Number(e.target.value))}/><ChildComponent data={calculatedData} /></div>);
};export default ParentComponent;
這里?calculatedData
?通過?useMemo
?緩存,當?num
?不變時,calculatedData
?的引用保持不變,ChildComponent
?不會因為?props
?的引用變化而重新渲染。
順便提一下【useCallback】:
當父組件向子組件傳遞一個函數作為?props
,并且子組件使用?React.memo
?包裹時,useCallback
?可以確保該函數的引用在依賴項不變時保持穩定,從而避免子組件因為函數引用的改變而進行不必要的重新渲染。
import React, { useState, useCallback, memo } from 'react';// 使用 React.memo 包裹子組件
const ChildComponent = memo(({ onClick }) => {console.log('ChildComponent 渲染');return <button onClick={onClick}>點擊我</button>;
});const ParentComponent = () => {const [count, setCount] = useState(0);// 使用 useCallback 緩存函數const handleClick = useCallback(() => {setCount(count + 1);}, [count]);return (<div><p>計數: {count}</p><ChildComponent onClick={handleClick} /></div>);
};export default ParentComponent;