React中useMemo和useCallback的使用小結(jié)
前言
在React函數(shù)組件中,每次狀態(tài)更新都會(huì)導(dǎo)致整個(gè)組件函數(shù)重新執(zhí)行。如果組件內(nèi)部有復(fù)雜的計(jì)算或者傳遞了回調(diào)函數(shù)給子組件,可能會(huì)引發(fā)不必要的性能開(kāi)銷。React為我們提供了兩個(gè)重要的Hook:useMemo和useCallback,用于緩存計(jì)算結(jié)果和函數(shù)引用,從而優(yōu)化組件性能。本文將從淺入深,結(jié)合實(shí)際代碼,帶你徹底理解這兩個(gè)Hook的使用場(chǎng)景和原理。
1. 性能優(yōu)化的必要性
我們先看一個(gè)簡(jiǎn)單的例子:
function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const list = ['apple', 'banana', 'orange', 'pear'];
// 每次渲染都會(huì)重新執(zhí)行filter
const filterList = list.filter(item => {
// 測(cè)試fliter是否執(zhí)行
console.log("filter執(zhí)行了")
return item.includes(keyword)
});
return (
<div>
<input value={keyword} onChange={e => setKeyword(e.target.value)} />
<button onClick={() => setCount(count + 1)}>count: {count}</button>
<ul>
{filterList.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
這里 filterList 依賴于 keyword,但每次 count 改變導(dǎo)致組件重新渲染時(shí),filterList 也會(huì)重新計(jì)算。如果 list 很大或者過(guò)濾邏輯復(fù)雜,這種不必要的計(jì)算就會(huì)影響性能。同樣,如果我們將一個(gè)函數(shù)作為 prop 傳遞給子組件,每次父組件渲染都會(huì)生成一個(gè)新函數(shù),導(dǎo)致子組件(即使使用 React.memo)也無(wú)法避免重新渲染。這正是 useMemo 和 useCallback 要解決的問(wèn)題。
效果圖

2. useMemo:緩存計(jì)算結(jié)果
useMemo 用于緩存一個(gè)計(jì)算后的值,只有當(dāng)依賴項(xiàng)發(fā)生變化時(shí),才會(huì)重新計(jì)算。
2.1 基本用法
const cachedValue = useMemo(computeFn, dependencies);
computeFn:純函數(shù),返回需要緩存的值。dependencies:依賴項(xiàng)數(shù)組,當(dāng)任意依賴項(xiàng)變化時(shí),重新執(zhí)行computeFn。
2.2 優(yōu)化列表過(guò)濾
改進(jìn)上面的例子:
import { useState, useMemo } from 'react';
export default function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const list = ['apple', 'banana', 'orange', 'pear'];
// 只有 keyword 變化時(shí)才重新過(guò)濾
const filterList = useMemo(() => {
console.log('filter執(zhí)行了'); // 依賴變化時(shí)打印
return list.filter(item => item.includes(keyword));
}, [keyword]);
return (
<div>
<input value={keyword} onChange={e => setKeyword(e.target.value)} />
<button onClick={() => setCount(count + 1)}>count: {count}</button>
<ul>
{filterList.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
現(xiàn)在,點(diǎn)擊 count 按鈕不會(huì)觸發(fā) filter 重新執(zhí)行,只有輸入框改變時(shí)才會(huì)。
效果圖

2.3 緩存昂貴計(jì)算
假設(shè)有一個(gè)非常耗時(shí)的函數(shù) slowSum:
function slowSum(n) {
console.log('計(jì)算中...');
let sum = 0;
for (let i = 0; i < n * 1000000; i++) {
sum += i;
}
return sum;
}
我們可以用 useMemo 將其結(jié)果緩存起來(lái):
const [num, setNum] = useState(0); const result = useMemo(() => slowSum(num), [num]);
這樣只有當(dāng) num 變化時(shí)才會(huì)重新計(jì)算,其他狀態(tài)變化不會(huì)觸發(fā) slowSum,這樣做我們就可以節(jié)約成本。
2.4 小結(jié)
useMemo適用于需要緩存計(jì)算結(jié)果的場(chǎng)景,比如過(guò)濾、排序、復(fù)雜數(shù)學(xué)運(yùn)算等。- 依賴數(shù)組要正確填寫(xiě),避免遺漏或多余依賴。
- 不要濫用,如果計(jì)算本身不昂貴,就沒(méi)有必要緩存。
3. useCallback:緩存函數(shù)引用
在React中,函數(shù)組件每次渲染都會(huì)重新創(chuàng)建內(nèi)部定義的函數(shù)。如果這個(gè)函數(shù)作為 prop 傳遞給子組件,即使子組件使用了 React.memo,也會(huì)因?yàn)槊看胃附M件傳遞的函數(shù)引用不同而導(dǎo)致子組件重新渲染。useCallback 就是用來(lái)緩存函數(shù)引用的。
3.1 基本用法
const cachedFn = useCallback(fn, dependencies);
fn:需要緩存的函數(shù)。dependencies:依賴項(xiàng)數(shù)組,當(dāng)依賴變化時(shí),重新創(chuàng)建函數(shù)。
3.2 配合 React.memo 優(yōu)化子組件
先看一個(gè)沒(méi)有優(yōu)化的例子:
import { useState, memo } from 'react';
const Child = memo(({ count, handleClick }) => {
console.log('子組件渲染了');
return <div onClick={handleClick}>子組件 count: {count}</div>;
});
export default function App() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(0);
// 每次 App 渲染,這里都會(huì)生成一個(gè)新函數(shù)
const handleClick = () => {
console.log('點(diǎn)擊了');
};
return (
<div>
<button onClick={() => setCount(count + 1)}>count+1</button>
<button onClick={() => setNum(num + 1)}>num+1</button>
<Child count={count} handleClick={handleClick} />
</div>
);
}
雖然 Child 用 memo 包裹,理論上只有 count 或 handleClick 變化時(shí)才會(huì)重新渲染。但由于 handleClick 每次都是新函數(shù),memo 淺比較發(fā)現(xiàn) handleClick 引用變了,導(dǎo)致子組件也會(huì)重新渲染。即使點(diǎn)擊的是 num 按鈕,子組件依然會(huì)渲染。
效果圖,可以看到當(dāng)我們點(diǎn)擊nums組件時(shí),我們的子組件依然會(huì)執(zhí)行

使用 useCallback 緩存 handleClick:
const handleClick = useCallback(() => {
console.log('點(diǎn)擊了');
}, []); // 空依賴表示函數(shù)永遠(yuǎn)不會(huì)重新創(chuàng)建
現(xiàn)在點(diǎn)擊 num 按鈕,handleClick 引用不變,子組件就不會(huì)重新渲染。
效果圖,可以看到這個(gè)適合當(dāng)我們點(diǎn)擊nums組件,子組件就不會(huì)執(zhí)行了

3.3 依賴項(xiàng)的作用
如果回調(diào)函數(shù)內(nèi)部使用了某些狀態(tài),需要將這些狀態(tài)添加到依賴數(shù)組中,否則函數(shù)內(nèi)部會(huì)一直使用舊的閉包值。
const handleClick = useCallback(() => {
console.log('當(dāng)前count:', count);
}, [count]); // 當(dāng) count 變化時(shí),重新生成函數(shù)
這樣既能保證函數(shù)引用在 count 不變時(shí)穩(wěn)定,又能在 count 變化時(shí)獲取最新值。
3.4 小結(jié)
useCallback主要用于將回調(diào)函數(shù)傳遞給經(jīng)過(guò)memo優(yōu)化的子組件,避免不必要的重繪。- 如果回調(diào)函數(shù)不依賴任何組件狀態(tài),依賴數(shù)組可以為空。
- 注意不要過(guò)度優(yōu)化,如果子組件很輕量或者渲染成本很低,不一定需要使用
useCallback。
4. useMemo 與 useCallback 的關(guān)系
useMemo緩存的是值,useCallback緩存的是函數(shù)。- 實(shí)際上,
useCallback(fn, deps)等價(jià)于useMemo(() => fn, deps)。 - 兩者都是通過(guò)閉包和依賴追蹤來(lái)實(shí)現(xiàn)緩存,目的都是減少不必要的計(jì)算或渲染。
5. 常見(jiàn)誤區(qū)與注意事項(xiàng)
5.1 依賴數(shù)組要完整
無(wú)論是 useMemo 還是 useCallback,都要確保依賴數(shù)組中包含了所有在回調(diào)/計(jì)算中使用的響應(yīng)式值(props、state、context等)。否則會(huì)因閉包捕獲舊值而產(chǎn)生 bug??梢允褂?eslint-plugin-react-hooks 自動(dòng)檢查依賴。
5.2 不要過(guò)早優(yōu)化
只有在確實(shí)存在性能瓶頸時(shí)才使用這兩個(gè) Hook。濫用會(huì)增加代碼復(fù)雜度,且緩存本身也有開(kāi)銷??梢酝ㄟ^(guò) React DevTools 的 Profiler 來(lái)識(shí)別需要優(yōu)化的組件。
5.3 緩存穩(wěn)定但非“純”的函數(shù)
如果 useCallback 的回調(diào)函數(shù)依賴于外部狀態(tài),但依賴數(shù)組為空,則函數(shù)內(nèi)部的變量會(huì)一直保持初始值,可能導(dǎo)致 bug。一定要根據(jù)實(shí)際依賴填寫(xiě)數(shù)組。
6. 總結(jié)
useMemo用于緩存計(jì)算值,避免每次渲染都重新執(zhí)行昂貴計(jì)算。useCallback用于緩存函數(shù)引用,配合React.memo避免子組件不必要的重新渲染。- 兩者都需要指定依賴項(xiàng),確保緩存內(nèi)容在依賴變化時(shí)更新。
- 性能優(yōu)化應(yīng)當(dāng)有的放矢,先測(cè)量后優(yōu)化,不要盲目使用。
通過(guò)合理使用這兩個(gè) Hook,我們可以讓 React 應(yīng)用在復(fù)雜場(chǎng)景下依然保持流暢。希望本文能幫助你深入理解 useMemo 和 useCallback,并在實(shí)際項(xiàng)目中正確應(yīng)用它們。
到此這篇關(guān)于React中useMemo和useCallback的具體使用的文章就介紹到這了,更多相關(guān)React useMemo useCallback內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 源碼分析React中useCallback和useMemo
- React中useMemo、useCallback的具體使用
- 詳解React中的useMemo和useCallback的區(qū)別
- React中useCallback useMemo使用方法快速精通
- React中useCallback useMemo到底該怎么用
- 解析React中useMemo與useCallback的區(qū)別
- react性能優(yōu)化useMemo與useCallback使用對(duì)比詳解
- react組件memo useMemo useCallback使用區(qū)別示例
- React useMemo和useCallback的使用場(chǎng)景
- React中useMemo 和 useCallback 的區(qū)別小結(jié)
相關(guān)文章
React如何使用sortablejs實(shí)現(xiàn)拖拽排序
這篇文章主要介紹了React如何使用sortablejs實(shí)現(xiàn)拖拽排序問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
全棧輕量級(jí)搭配之Remix Prisma Sqlite使用分析
這篇文章主要為大家介紹了全棧輕量級(jí)搭配之Remix Prisma Sqlite使用示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
React Native項(xiàng)目中使用Lottie動(dòng)畫(huà)的方法
這篇文章主要介紹了React Native 實(shí)現(xiàn)Lottie動(dòng)畫(huà)的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10
react?Table準(zhǔn)備Spin?Empty?ConfigProvider組件實(shí)現(xiàn)
這篇文章主要為大家介紹了react?Table準(zhǔn)備Spin、Empty、ConfigProvider組件實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-02-02
React為什么需要Scheduler調(diào)度器原理詳解
這篇文章主要為大家介紹了React為什么需要Scheduler調(diào)度器原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10

