React中useMemo和useCallback的使用小結
前言
在React函數組件中,每次狀態(tài)更新都會導致整個組件函數重新執(zhí)行。如果組件內部有復雜的計算或者傳遞了回調函數給子組件,可能會引發(fā)不必要的性能開銷。React為我們提供了兩個重要的Hook:useMemo和useCallback,用于緩存計算結果和函數引用,從而優(yōu)化組件性能。本文將從淺入深,結合實際代碼,帶你徹底理解這兩個Hook的使用場景和原理。
1. 性能優(yōu)化的必要性
我們先看一個簡單的例子:
function App() {
const [count, setCount] = useState(0);
const [keyword, setKeyword] = useState('');
const list = ['apple', 'banana', 'orange', 'pear'];
// 每次渲染都會重新執(zhí)行filter
const filterList = list.filter(item => {
// 測試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 改變導致組件重新渲染時,filterList 也會重新計算。如果 list 很大或者過濾邏輯復雜,這種不必要的計算就會影響性能。同樣,如果我們將一個函數作為 prop 傳遞給子組件,每次父組件渲染都會生成一個新函數,導致子組件(即使使用 React.memo)也無法避免重新渲染。這正是 useMemo 和 useCallback 要解決的問題。
效果圖

2. useMemo:緩存計算結果
useMemo 用于緩存一個計算后的值,只有當依賴項發(fā)生變化時,才會重新計算。
2.1 基本用法
const cachedValue = useMemo(computeFn, dependencies);
computeFn:純函數,返回需要緩存的值。dependencies:依賴項數組,當任意依賴項變化時,重新執(zhí)行computeFn。
2.2 優(yōu)化列表過濾
改進上面的例子:
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 變化時才重新過濾
const filterList = useMemo(() => {
console.log('filter執(zhí)行了'); // 依賴變化時打印
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>
);
}
現在,點擊 count 按鈕不會觸發(fā) filter 重新執(zhí)行,只有輸入框改變時才會。
效果圖

2.3 緩存昂貴計算
假設有一個非常耗時的函數 slowSum:
function slowSum(n) {
console.log('計算中...');
let sum = 0;
for (let i = 0; i < n * 1000000; i++) {
sum += i;
}
return sum;
}
我們可以用 useMemo 將其結果緩存起來:
const [num, setNum] = useState(0); const result = useMemo(() => slowSum(num), [num]);
這樣只有當 num 變化時才會重新計算,其他狀態(tài)變化不會觸發(fā) slowSum,這樣做我們就可以節(jié)約成本。
2.4 小結
useMemo適用于需要緩存計算結果的場景,比如過濾、排序、復雜數學運算等。- 依賴數組要正確填寫,避免遺漏或多余依賴。
- 不要濫用,如果計算本身不昂貴,就沒有必要緩存。
3. useCallback:緩存函數引用
在React中,函數組件每次渲染都會重新創(chuàng)建內部定義的函數。如果這個函數作為 prop 傳遞給子組件,即使子組件使用了 React.memo,也會因為每次父組件傳遞的函數引用不同而導致子組件重新渲染。useCallback 就是用來緩存函數引用的。
3.1 基本用法
const cachedFn = useCallback(fn, dependencies);
fn:需要緩存的函數。dependencies:依賴項數組,當依賴變化時,重新創(chuàng)建函數。
3.2 配合 React.memo 優(yōu)化子組件
先看一個沒有優(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 渲染,這里都會生成一個新函數
const handleClick = () => {
console.log('點擊了');
};
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 變化時才會重新渲染。但由于 handleClick 每次都是新函數,memo 淺比較發(fā)現 handleClick 引用變了,導致子組件也會重新渲染。即使點擊的是 num 按鈕,子組件依然會渲染。
效果圖,可以看到當我們點擊nums組件時,我們的子組件依然會執(zhí)行

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

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

