目錄
(一)memo API
1.先想一個情景
2.用法
(1)props傳入普通數據類型的情況?
(2)props傳入對象的情況
(3)props傳入函數的情況
(4)使用自定義比較函數
3.什么時候使用memo?
(二)useMemo Hook
1.用法
2.useMemo實現組件記憶化?
3.useMemo實現函數記憶化
(三)useCallback Hook
1.用法
(四)總結
(一)memo API
memo – React 中文文檔
1.先想一個情景
function App() {const [count, setCount] = useState(0)const [name, setName] = useState('csq')return (<><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button><Name name={name}></Name></>)
}function Name(props){console.log('Name組件重新渲染了');return (<div>{props.name}</div>)
}
點擊按鈕count改變后,整個App組件會重新渲染,即使Name組件的props沒有改變,Name組件也被重新渲染了
如果一個過于復雜的組件的props沒有改變,那么重新渲染它會增加渲染負擔;
通常情況下,只要該組件的 props 沒有改變,這個記憶化版本就不會在其父組件重新渲染時重新渲染。這就稱之為記憶化,是一種性能優化的辦法
因此,memo api的作用就體現出來了:
memo
允許你的組件在 props 沒有改變的情況下跳過重新渲染。?
2.用法
const xxx = memo(function xxx(props){...},
arePropsEqual?
)?
將需要進行記憶化的組件用memo包裹起來,通過arePropsEqual函數判斷props是否變化(可自定義,不寫就默認使用Object.is()判斷),然后返回新的react組件
(1)props傳入普通數據類型的情況?
import { memo } from 'react'
// 使用memo
function App() {const [count, setCount] = useState(0)const [name, setName] = useState('csq')return (<><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button><Name name={name}></Name></>)
}// 不傳入arePropsEqual函數就默認用Object.is判斷
const Name = memo(function Name(props){console.log('Name組件重新渲染了');return (<div>{props.name}</div>)
})
這樣一來,Name組件在父組件重新渲染的時候就不會跟著渲染啦
(2)props傳入對象的情況
上面提到了,不自定義比較函數的話,memo采用Object.is()方法來比較props是否變化
但是!Object.is()只能比較淺層數據是否變化,如果對復雜數據類型進行比較會發現:
結果為false!這表明了傳入復雜數據類型的prop仍會導致memo組件重新渲染?
import { memo } from 'react'
// 使用memo
function App() {const [count, setCount] = useState(0)return (<><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button><Name data={{name:'csq'}}></Name></>)
}
// 不傳入arePropsEqual函數就默認用Object.is判斷
const Name = memo(function Name(props){console.log('Name組件重新渲染了',props);return (<div>{props.data.name}</div>)
})
重新渲染了又?
如何避免??
為了最大化使用 memo
的效果,應該盡量減少 props 的變化次數?
- 最小化props:將對象拆分開再傳
- 使用useMemo Hook(后面會講到)
- 指定自定義比較函數(后面會講到)
(3)props傳入函數的情況
函數也是復雜數據類型中的一種,所以傳入的就算是同一個函數,父組件的重新渲染也會引起記憶化的組件重新渲染,這樣memo就失效了!
針對props傳入的函數,就要采用另一個Hook:useCallback?來實現組件的記憶化,后面第三節就會講到了
(4)使用自定義比較函數
一定要確保使用比較函數的時間比重新渲染要快,不然寫個比較函數還浪費那么久時間簡直白白干了
const Chart = memo(function Chart({ dataPoints }) {// ...
}, arePropsEqual);function arePropsEqual(oldProps, newProps) {return (oldProps.dataPoints.length === newProps.dataPoints.length &&oldProps.dataPoints.every((oldPoint, index) => {const newPoint = newProps.dataPoints[index];return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;}));
}
注意:比較函數內一定要將props的所有prop都比較到,包括函數。
避免在
arePropsEqual
中進行深比較,除非你 100% 確定你正在處理的數據結構具有已知有限的深度。深比較可能會變得非常緩慢,并且如果有人稍后更改數據結構,這可能會卡住你的應用數秒鐘。
簡而言之就是少用,有那個功夫用用hook肯定更快啦?
3.什么時候使用memo?
memo api并不是是個組件就給安上的,首先要考慮到組件的重新渲染對頁面會不會造成影響,比如有動畫的組件:重新渲染會導致動畫重新播放,如果不想變化的話就加個memo
如果組件的重新渲染沒有什么延遲,當然也不需要用memo?
(二)useMemo Hook
useMemo – React 中文文檔
useMemo
能在每次重新渲染的時候能夠緩存計算的結果。
1.用法
useMemo(calculateValue, dependencies)
?
-
calculateValue
:要緩存計算值的函數。React 將會在首次渲染時調用該函數;在之后的渲染中,如果dependencies
沒有發生變化,React 將直接返回相同值。否則,將會再次調用calculateValue
并返回最新結果,然后緩存該結果以便下次重復使用。 -
dependencies
:所有在calculateValue
函數中使用的響應式變量組成的數組。React 使用 Object.is 將每個依賴項與其之前的值進行比較。?
舉例:用counterFilter模擬運行時間長的函數,每次點擊todo長度加1按鈕都會等待0.5s
不使用useMemo的話,即使len不發生改變,count發生改變時,也會重新渲染運行counterFilter
使用useMemo()后,當len發生改變時,才會重新計算todoSlice的值,而點擊count+1按鈕不會造成卡頓
import { useState, useMemo } from 'react'
function App() {const [count,setCount] = useState(1)const [len, setLen] = useState(2)// 這里todos是個數組,也過不了Object.is(),所以套一層useMemoconst todos = useMemo(()=>([1, 2, 3, 4, 5, 6, 7, 8, 9]),[])const todoSlice = useMemo(() => counterFilter(todos, len),[todos, len])return (<><button onClick={() => { setLen(len + 1) }}>todo長度加1</button><button onClick={() => { setCount(count + 1) }}>count加1</button>{todoSlice.map(value => (<p key={value}>todo: {value}</p>))}<Counter count={count}></Counter></>)
}const Counter = function Name(props) {return (<div>{props.count}</div>)
}function counterFilter(todos, length) {let startTime = performance.now();while (performance.now() - startTime < 500) {// 在 500 毫秒內不執行任何操作以模擬極慢的代碼}return todos.filter((value, index) => index < length)
}
這樣一用,哎喲這不是vue的計算屬性嘛,蠻有意思蠻有意思
不過useMemo Hook設計來是用于存儲運算時間長的計算結果以避免重復渲染的一種優化手段(我自認為是這樣)
2.useMemo實現組件記憶化?
提到避免重復渲染,useMemo可以結合上文的memo api實現記憶化,解決props傳入對象破環記憶化的情況
function Page() {const [name, setName] = useState('Taylor');const [age, setAge] = useState(42);const person = useMemo(() => ({ name, age }),[name, age]);return <Profile person={person} />;
}const Profile = memo(function Profile({ person }) {// ...
});
3.useMemo實現函數記憶化
函數也是一種對象,所以當然能用useMemo實現記憶化?
export default function Page({ productId, referrer }) {const handleSubmit = useMemo(() => {return (orderDetails) => {post('/product/' + productId + '/buy', {referrer,orderDetails});};}, [productId, referrer]);return <Form onSubmit={handleSubmit} />;
}
這樣看起來蠢蠢的,函數又套函數!
術業有專攻,記憶函數要使用react專門提供的一個hook,也就是下面要講到的
(三)useCallback Hook
useCallback – React 中文文檔
useCallback
是一個允許你在多次渲染中緩存函數的 React Hook。?
1.用法
const cachedFn = useCallback(fn, dependencies)
用法和useMemo一樣,傳入兩個參數
-
fn
:想要緩存的函數。React 將會在初次渲染而非調用時返回該函數。當進行下一次渲染時,如果dependencies
相比于上一次渲染時沒有改變,那么 React 將會返回相同的函數。否則,React 將返回在最新一次渲染中傳入的函數,并且將其緩存以便之后使用。React 不會調用此函數,而是返回此函數。你可以自己決定何時調用以及是否調用。 -
dependencies
:有關是否更新fn
的所有響應式值的一個列表。響應式值包括 props、state,和所有在你組件內部直接聲明的變量和函數。 -
返回值:返回想要緩存的函數。
還是用memo那兒的例子:
函數setName本質是對象,即使傳入的函數沒有改變,Object.is()判斷也是false,這樣就導致memo失去了記憶化
function App() {const [count, setCount] = useState(0)const [name, setName] = useState('csq')return (<><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button><Name name={name} setName={(name)=>{setName(name)}}></Name></>)
}const Name = memo(function Name(props){console.log('Name組件重新渲染了');return (<><div>{props.name}</div><button onClick={() => { props.setName(props.name+'!') }}>修改名字</button></>)
})
直接上useCallback!
這個useCallback里的依賴數組為空,是因為setName這個函數本身就是不變的,不受變量影響
import { useState, memo, useCallback } from 'react'
function App() {const [count, setCount] = useState(0)const [name, setName] = useState('csq')const setNameCallback = useCallback((name) => { setName(name) },[])return (<><div>{count}</div><button onClick={()=>{setCount(count+1)}}>加1</button><Name name={name} setName={setNameCallback}></Name></>)
}const Name = memo(function Name(props){console.log('Name組件重新渲染了');return (<><div>{props.name}</div><button onClick={() => { props.setName(props.name+'!') }}>修改名字</button></>)
})
(四)總結
今天學了memo、useMemo、useCallback,感覺花太多時間在memo上了,后面兩個hook就寫的稍微有點水,有點漏洞
以后應該多花點時間在敲代碼上,感覺寫博客寫太久了
memo:用于實現組件記憶化,如果props傳遞的是對象,那么memo就沒意義了
useMemo:立即執行函數返回結果并保存。可以和memo結合使用,傳遞props不變則不重新渲染;也可以用于存儲較花費事件的數據;
useCallback:和memo結合使用,是保存函數但不會執行。
總結來說都是用于性能優化,?不能依靠這些來避免本身代碼的問題