React 中 useCallback 的基本使用和原理解析
React 中 useCallback 的基本使用方法
useCallback 是 React 的一個核心 Hook,用于?緩存函數定義?,避免組件重新渲染時重復創建函數實例。以下是其基本使用方法:
1. 基本語法
const memoizedCallback = useCallback(
() => {
// 函數邏輯 (例如更新狀態、調用API等)
doSomething(a, b);
},
[a, b] // 依賴項數組
);
- ?第一個參數?:需要緩存的函數。
- ?第二個參數?:依賴項數組(Dependency Array),當數組中的變量變化時,函數會重新創建。
2. 核心作用
- ?避免不必要的函數重建?:默認情況下,組件每次渲染都會創建新的函數實例,使用
useCallback后可復用函數。 - ?優化子組件渲染?:當緩存的函數作為 props 傳遞給子組件(配合
React.memo)時,可避免子組件不必要的重渲染?。
3. 使用示例
import React, { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 緩存函數:依賴項為空數組,函數只創建一次
const increment = useCallback(() => {
setCount(prev => prev + 1); // 使用函數式更新避免閉包問題
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
- 依賴項
[]表示函數僅在組件初次渲染時創建。 - 使用
setCount(prev => prev + 1)替代setCount(count + 1)可避免閉包陷阱(函數捕獲過時狀態)?。
4. 適用場景
useCallback,本質上是用于緩存函數。
如果函數,是以props的方式,傳遞給子組件,為了每次避免子組件的渲染,建議使用useCallback進行包裹。
但是每一次,使用useCallback,我們考慮的首要問題是,這樣真的優化了組件的性能嗎?其實大多數場景,如果不是類似列表渲染的場景,這樣不一定會優化了性能。
也就是,函數作為props傳遞給性能敏感的子組件的場景,才是使用useCallback的時候。
useCallback 的原理解析
useCallback的主要目的是在依賴項不變的情況下,返回同一個函數引用,避免函數重復創建,從而優化性能。useCallback它會在首次渲染時(或依賴項變化時)創建一個新的函數,并將其緩存起來。在后續渲染中,如果依賴項沒有變化,則返回緩存的函數;否則,就重新創建函數并更新緩存。- 簡易的偽代碼,可能如下所示
let lastDeps; // 上一次的依賴項
let lastCallback; // 上一次緩存的函數
function useCallback(callback, deps) {
if (lastDeps === undefined) {
// 第一次調用
lastDeps = deps;
lastCallback = callback;
return callback;
}
// 檢查依賴項是否變化
const hasChanged = deps.some((dep, index) => dep !== lastDeps[index]);
if (hasChanged) {
lastDeps = deps;
lastCallback = callback;
}
return lastCallback;
}
每次掉用useCallback,返回的函數,取決于依賴項有沒有發生變化。
React內部是咋樣的呢?
1、Fiber 節點存儲機制
React 在 Fiber 節點(組件實例對應的數據結構)中維護一個 memorizedState 鏈表,專門存儲 Hooks 狀態。
function updateCallback(callback, deps) {
const hook = updateWorkInProgressHook(); // 獲取當前 Hook 節點
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState; // 讀取緩存的上次狀態
// 依賴項對比:使用淺比較(shallow equal)
if (prevState !== null && areHookInputsEqual(nextDeps, prevState[1])) {
return prevState[0]; // 返回緩存的函數
}
// 依賴變化:緩存新函數
hook.memoizedState = [callback, nextDeps];
return callback;
}
2、依賴項對比算法
源碼中的 areHookInputsEqual 對依賴數組進行淺比較(類似 Object.is):
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) return false;
for (let i = 0; i < prevDeps.length; i++) {
if (!Object.is(nextDeps[i], prevDeps[i])) {
return false;
}
}
return true;
}
這種優化避免了深度比較的性能損耗
浙公網安備 33010602011771號