前言
剛開始使用react hooks熙含,大量使用useCallback,認(rèn)為是性能優(yōu)化處理艇纺;后來發(fā)現(xiàn)有些場(chǎng)景使用useCallback反而造成性能問題怎静。認(rèn)真了解相關(guān)資料后邮弹,輸出以下總結(jié)。
結(jié)論在前
官方文檔不推薦大量使用蚓聘。
推薦使用原則:
不懂的不用腌乡;懂的少用。最好是遇到需要性能優(yōu)化時(shí)再去處理夜牡。
理由:
- react re-render的渲染性能很高与纽,大部分時(shí)候足以包容大部分web項(xiàng)目出現(xiàn)的hooks重復(fù)渲染引起的性能消耗。主要因?yàn)閞e-render作用于vertual dom塘装,而不是實(shí)際的dom急迂,所以re-render的渲染性能很高。
- 相對(duì)于花費(fèi)額外的開發(fā)成本關(guān)注細(xì)微的性能優(yōu)化蹦肴,更推薦減少訂閱視圖無關(guān)的數(shù)據(jù)僚碎,以避免重復(fù)渲染。
- 從react hooks設(shè)計(jì)上來說阴幌,本身就接受re-render是常態(tài)的事實(shí)勺阐,否則就應(yīng)該把useCallback和useMemo放在架構(gòu)里使用,而不是手動(dòng)調(diào)用矛双。大量使用useCallback和useMemo渊抽,反而違背了最小知識(shí)原則和接口隔離原則
- useCallback的設(shè)計(jì)初衷并非解決組件內(nèi)部函數(shù)多次創(chuàng)建的問題,而是減少子組件的不必要重復(fù)渲染
useCallback和useMemo
相同點(diǎn)
通過緩存函數(shù)(對(duì)象)背零,減少re-render腰吟。僅依賴項(xiàng)發(fā)生變化時(shí),才觸發(fā)re-render
不同點(diǎn)
- useCallback緩存函數(shù)
- useMemo緩存對(duì)象
useCallback(fn, deps)
useMemo(() => fn, deps)
作用
在react hooks中徙瓶,由于state更新或父組件更新等原因都容易引起的組件re-render毛雇,都會(huì)導(dǎo)致函數(shù)和對(duì)象的重復(fù)聲明和計(jì)算。useCallback和useMemo通過緩存函數(shù)或?qū)ο笳煺颍_(dá)成僅初始化或依賴項(xiàng)發(fā)生變化時(shí)灵疮,才會(huì)觸發(fā)對(duì)應(yīng)函數(shù)或?qū)ο蟮膔e-render的作用。
Re-render
在react class對(duì)應(yīng)的生命周期shouldComponentUpdate可以檢測(cè)到組件內(nèi)部的部分更新壳繁;同樣的react hooks中為了可以檢測(cè)到狀態(tài)的更新震捣,引入了re-render。
對(duì)于函數(shù)組件的 re-render闹炉,大致分為以下三種情況:
- 組件本身使用 useState 或 useReducer 更新蒿赢,引起的 re-render;
- 父組件更新引起的 re-render渣触;
- 組件本身使用了 useContext羡棵,context 更新引起的 re-render。
三種情況場(chǎng)景
性能優(yōu)化
常規(guī)使用
count每次更新時(shí)嗅钻,都會(huì)導(dǎo)致Hello組件的渲染皂冰,對(duì)于Hello組件來說店展,這是無效的更新
const Hello = ({ name }) => {
console.log("hello render");
return<div>hello {name}</div>;
};
const App = () => {
console.log("app render");
const [count, addCount] = useState(0);
return (
<div className="app">
<Hello name="react" />
<div className="counter-num">{count}</div>
<button
onClick={() => {
addCount(count + 1);
}}
>
add
</button>
</div>
);
};
優(yōu)化方案
- 將更新部分抽離成單獨(dú)組件
// Counter組件內(nèi)部的渲染觸發(fā)Hello進(jìn)行無效的渲染
const App = () => {
return (
<div className="app">
<Hello name="react" />
<Counter />
</div>
);
};
- 將不需要 re-render 的部分抽離,以插槽形式渲染(children)
// App 組件預(yù)留 children 位秃流, count數(shù)據(jù)更新不會(huì)觸發(fā)children的更新
const App = ({ children }) => {
const [count, addCount] = useState(0);
return (
<div className="app">
{children}
<div className="counter-num">{count}</div>
<button
onClick={() => {
addCount(count + 1);
}}
>
add
</button>
</div>
);
};
// 使用
<App>
<Hello name="react" />
</App>
- React.memo
問題:Hello組件props沒有更新赂蕴,但由于父組件count的更新,觸發(fā)re-render
解決辦法:
// Hello組件memo化舶胀,count數(shù)據(jù)變化時(shí)概说,Hello 組件是不會(huì)re-render的。除非Hello組件的props更新峻贮。
const Hello = React.memo(({ name }) => {
console.log("hello render");
return<div>hello {name}</div>;
});
- useCallback
問題:count變化席怪,引起clickHandler重新計(jì)算;onClick更新纤控,又引起Hello組件更新
// 新增 onClick 處理函數(shù)
const Hello = React.memo(({ name, onClick }) => {
console.log("hello render");
return<div onClick={onClick}>hello {name}</div>;
});
const App = ({ children }) => {
console.log("counter render");
const [count, addCount] = useState(0);
// 新增處理函數(shù)
const clickHandler = () => {
console.log("hello click");
};
return (
<div className="counter">
<Hello name="react" onClick={clickHandler} />
<div className="counter-num">{count}</div>
<button
onClick={() => {
addCount(count + 1);
}}
>
add
</button>
</div>
);
};
解決辦法:
// 新增處理函數(shù)挂捻,使用 useCallback 緩存起來
const clickHandler = useCallback(() => {
console.log("hello click");
}, []);
例外:useCallback無效場(chǎng)景
// 新增處理函數(shù),使用 useCallback 緩存起來
// 在 callback 函數(shù)中使用 count
// 并將 count 添加進(jìn)依賴
// 只要 count 更新船万,callback 函數(shù)又將更新刻撒,useCallback 就沒什么用了
const clickHandler = useCallback(() => {
console.log("count: ", count);
}, [count]);
useCallback/useMemo 的使用誤區(qū)
產(chǎn)生誤區(qū)的原因是useCallback的設(shè)計(jì)初衷并非解決組件內(nèi)部函數(shù)多次創(chuàng)建的問題,而是減少子組件的不必要重復(fù)渲染
總結(jié)
1.不推薦大量使用useCallback和useMemo耿导,更推薦通過不訂閱視圖無關(guān)的數(shù)據(jù)声怔、細(xì)化組件粒度(將更新部分抽離成單獨(dú)組件、將不需要 re-render 的部分抽離舱呻,以插槽形式渲染)醋火,減少re-render影響范圍。
2.useCallback返回作為props的函數(shù)箱吕,需要搭配memo使用芥驳。
參考文獻(xiàn)
https://mp.weixin.qq.com/s/S9DNxUxtlbwRrwC_YEc26w
https://www.zhihu.com/question/390974405