歡迎來(lái)到我的博客
??博主是一名大學(xué)在讀本科生,主要學(xué)習(xí)方向是前端。
??目前已經(jīng)更新了【Vue】、【React–從基礎(chǔ)到實(shí)戰(zhàn)】、【TypeScript】等等系列專欄
??目前正在學(xué)習(xí)的是?? R e a c t / 小程序 React/小程序 React/小程序??,中間穿插了一些基礎(chǔ)知識(shí)的回顧
??博客主頁(yè)??codeMak1r.小新的博客本文被專欄【React–從基礎(chǔ)到實(shí)戰(zhàn)】收錄
??堅(jiān)持創(chuàng)作??,一起學(xué)習(xí)??,碼出未來(lái)???????!
前言
useMemo / useCallback都是React內(nèi)置的用于性能優(yōu)化的hook,它們常常被開(kāi)發(fā)人員用來(lái)包裹(緩存memory),但是真的是所有的數(shù)據(jù)、函數(shù)、變量都需要使用useMemo / useCallback去緩存嗎?
可直接看結(jié)論。
useMemo / useCallback都是用以性能優(yōu)化的hook,開(kāi)發(fā)者經(jīng)常擔(dān)心兩次渲染間的重復(fù)計(jì)算,而去過(guò)度使用useMemo / useCallback,擔(dān)心性能問(wèn)題的開(kāi)發(fā)者們,給幾乎每個(gè)變量都套上了useMemo,給每個(gè)函數(shù)都套上了useCallback……其實(shí)這是不可取的,這讓代碼看起來(lái)像是必須使用這兩個(gè)hook去優(yōu)化一樣,無(wú)處不在。
本文希望通過(guò)分析 useMemo/useCallback 的目的、方式、成本,以及具體使用場(chǎng)景,幫助開(kāi)發(fā)者正確的決定如何適時(shí)的使用他們。趕時(shí)間的讀者可以直接拉到底部看結(jié)論。
我們先從 useMemo/useCallback 的目的說(shuō)起。
何時(shí)應(yīng)該使用useMemo / useCallback ?
防止不必要的 effect
小新在編碼的過(guò)程中,如果effect有依賴的變量,我就會(huì)把effect里的內(nèi)容提到effect外面,包裝成一個(gè)函數(shù),再用useCallback去緩存這個(gè)函數(shù),那么只要這個(gè)變量不變化,effect依賴的這個(gè)函數(shù)也不會(huì)改變(不使用useCallback緩存的話,此函數(shù)的內(nèi)存地址可能會(huì)發(fā)生變化,哪怕其內(nèi)部不改變)。
const Component = () => {
const a = React.useMemo(() => ({ test: 1 }), [])
React.useEffect(() => {
// dosomthing
}, [a])
return (
<div>{a.test}</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component />);
只有a的值改變時(shí),dosomthing
才會(huì)重新觸發(fā),而a被useMemo
緩存了,這就導(dǎo)致非必要時(shí),effect不會(huì)重新創(chuàng)建,這是好的優(yōu)化;
而useCallback
也是一樣的,(useCallback其實(shí)是useMemo的語(yǔ)法糖)
const Component = () => {
const ajax = React.useCallback(() => {
console.log('^ajax somthing^!')
}, [])
React.useEffect(() => {
// dosomthing
ajax()
}, [ajax])
return (
<div></div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component />);
此代碼段中的Component組件,只有當(dāng)ajax函數(shù)變化時(shí)才會(huì)重新創(chuàng)建一個(gè)effect,這就導(dǎo)致,我們可以把僅需要在頁(yè)面首次加載時(shí)發(fā)送的ajax請(qǐng)求封裝成一個(gè)函數(shù),并且用useCallback
優(yōu)化緩存下來(lái),這是好的優(yōu)化;
防止不必要的re-render
我們首先思考,當(dāng)什么情況出現(xiàn)時(shí),組件才會(huì)re-render
?
- 當(dāng)本身的props或state改變時(shí);
- context上下文的value改變時(shí),使用該值的組件都會(huì)重新render;
- 當(dāng)父組件重新render時(shí),哪怕傳入子組件的props沒(méi)有發(fā)生改變,子組件(們)也會(huì)隨著父組件的render,重新render;
第三個(gè)re-render
經(jīng)常被開(kāi)發(fā)者忽視,其實(shí)這一點(diǎn)很重要??!
例如,
const Component = () => {
const [state, setState] = React.useState(1);
const onClick = React.useCallback(() => {
console.log('^click somthing^!')
}, []);
return (
// 哪怕onClick使用了useCallback緩存優(yōu)化,但是自組件仍會(huì)re-render
<Child onClick={onClick} />
)
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component />);
哪怕onClick使用了useCallback緩存優(yōu)化,但是自組件仍會(huì)re-render。這里的useCallback似乎是無(wú)效的。
那么,怎么讓其生效呢?
我們可以搭配React.memo
去使用:
const PageMemoized = React.memo(Page);
React.memo本質(zhì)是一個(gè)HOC
,它接受一個(gè)組件作為參數(shù)。被memo包裹的Page組件,會(huì)在Page組件的父組件Component重新render時(shí),對(duì)比傳入Page組件的props( 淺比較,復(fù)雜對(duì)象只比較第一層),若props沒(méi)有發(fā)生改變,則Pages組件就不會(huì)re-render
。
所以, 必須同時(shí)緩存 onClick 和組件本身,才能實(shí)現(xiàn) Page 不觸發(fā) re-render。
PageMemoized會(huì)在父組件重新render時(shí),淺比較傳入的onClick是否變化再?zèng)Q定PageMemoized組件是否需要re-render
,但是onClick正好被useCallback緩存了,所以這里的子組件不會(huì)re-render(●–●)
但是,如果PageMemoized組件從父組件不止接受了onClick一個(gè)prop,那么前面的優(yōu)化就前功盡棄。比如,
// 省略重復(fù)代碼
<PageMemoized onClick={onClick} value={[1, 2, 3]} />
每次父組件重新re-render時(shí),傳入子組件的onClick函數(shù)雖然沒(méi)有改變(useCallback的功勞),但是value并沒(méi)有做任何緩存,此時(shí),子組件PageMemoized
,還是逃脫不了re-render的命運(yùn)……
怎么解決呢?
// 省略重復(fù)代碼
const value = useMemo(() => {
return [1, 2, 3]
}, [])
// ...
<PageMemoized onClick={onClick} value={[1, 2, 3]} />
這樣的話,value變量也被緩存起來(lái)了,父組件re-render時(shí),自組件并沒(méi)有re-render。
由此我們知道, 必須同時(shí)緩存 所有的prop 和組件本身,才能實(shí)現(xiàn)子組件 不觸發(fā) re-render。
如何判斷子組件是否需要緩存?
如果所有的子組件都需要緩存,那未免也太麻煩了……不光需要memo子組件,還需要將現(xiàn)有的props都進(jìn)行緩存,并且還包括了后續(xù)編碼可能出現(xiàn)的其他props……
除此之外,還有更嚴(yán)重的后果,如果項(xiàng)目中的組件緩存過(guò)多的話,可能會(huì)導(dǎo)致 項(xiàng)目在首次初始化時(shí)因?yàn)榻M件緩存被拖慢渲染時(shí)間。
所以,局部的,有選擇的去使用memo,比全局都使用memo更加恰當(dāng)、更加優(yōu)雅。
至于怎樣判斷組件的渲染成本,可以借助React Devtool
等工具去判斷,或者根據(jù)開(kāi)發(fā)者經(jīng)驗(yàn)人工判斷。
防止不必要的計(jì)算
React官方文檔介紹:
useMemo返回一個(gè) memoized 值。
把“創(chuàng)建”函數(shù)和依賴項(xiàng)數(shù)組作為參數(shù)傳入 useMemo,它僅會(huì)在某個(gè)依賴項(xiàng)改變時(shí)才重新計(jì)算 memoized 值。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開(kāi)銷
的計(jì)算。
什么才是高開(kāi)銷呢?
借助前端經(jīng)典面試題提供的測(cè)試用例,對(duì)包含 250 個(gè) item 的數(shù)組 countries 進(jìn)行排序、渲染,并計(jì)算耗時(shí)。結(jié)果發(fā)現(xiàn),排序耗時(shí)僅用了4ms,而渲染這些List卻用了20ms,5倍的差距!而日常開(kāi)發(fā)中,大部分情況下都是,計(jì)算的數(shù)據(jù)更少,而渲染的組件更多。這也就說(shuō)明了, 大部分情況下,真正的性能瓶頸不是計(jì)算,而是渲染。 所以應(yīng)該把useMemo用在渲染昂貴的組件上,而不是計(jì)算上。
那為什么不給所有的組件都使用useMemo呢?前面也說(shuō)了,緩存會(huì)影響項(xiàng)目初始化的速度,而且可能會(huì)導(dǎo)致與PureComponent
相同的問(wèn)題,傳入子組件的prop淺層并無(wú)變化,于是被useMemo包裹的子組件并不會(huì)re-render,但其實(shí)此時(shí)正需要它re-render。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-786263.html
結(jié)論
- 大部分的 useMemo 和 useCallback 都應(yīng)該移除,他們可能沒(méi)有帶來(lái)任何性能上的優(yōu)化,反而增加了程序首次渲染的負(fù)擔(dān),并增加程序的復(fù)雜性。
- 使用 useMemo 和 useCallback 優(yōu)化子組件 re-render 時(shí),必須同時(shí)滿足:
- 子組件被React.memo 或 useMemo 緩存;
- 子組件所有的prop都被緩存。
- 不推薦默認(rèn)給所有組件都使用緩存,大量組件初始化時(shí)被緩存,可能導(dǎo)致過(guò)多的內(nèi)存消耗,并影響程序初始化渲染的速度。
專欄訂閱入口【React–從基礎(chǔ)到實(shí)戰(zhàn)】文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-786263.html
到了這里,關(guān)于【1024用代碼改變世界】useMemo 和 useCallback|React.memo使用場(chǎng)景的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!