[英]why use 'ref' when you use useCallback instead of using useCallback directly
我正在研究 React 項目,並且正在調查一些庫。 我發現他們使用的“useCallback”與我使用的不同。下面是代碼部分。我仍然認為這段代碼與直接使用“useCallback”沒有區別
// Saves incoming handler to the ref in order to avoid "useCallback hell"
export function useEventCallback<T, K>(handler?: (value: T, event: K) => void): (value: T, event: K) => void {
const callbackRef = useRef(handler);
useEffect(() => {
callbackRef.current = handler;
});
return useCallback((value: T, event: K) => callbackRef.current && callbackRef.current(value, event), []);
}
所以我的問題是'useCallback hell'是什么意思? 以這種方式使用“useCallback”有什么好處?
// 順便說一句:我在反應文檔中找到了一個類似的示例。 但我仍然無法理解https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-chang-value-from-usecallback
當您執行正常的useCallback
時,您必須傳入一個依賴數組,其中包含 function 使用的變量。 當其中一個發生變化時,記憶就會中斷。 在許多情況下這很好,但有時你的記憶總是在中斷(因為你依賴於一直在變化的值)。 發生這種情況時, useCallback
提供的好處很少或根本沒有。
您展示的代碼的目標是記憶化永不中斷,即使您有復雜的依賴關系。 請注意,當它調用useCallback
時,它傳入一個空的依賴數組[]
。 結合使用 ref 能夠跟蹤最新的handler
是什么。 然后當最終調用 function 時,它將檢查 ref 以獲取最新的handler
並調用它。 最新的handler
在其閉包中具有最新的值,因此它的行為符合預期。
這段代碼確實實現了永不破壞記憶的目標。 但是,需要謹慎使用。 如果你使用react的並發渲染,在渲染過程中調用useEventCallback返回的useEventCallback
,會得到一些意想不到的結果。 只有在渲染之外調用 function 是安全的,例如在事件回調中,這就是他們將其命名為useEventCallback
的原因。
舉例說明:
function App() {
const [count, setCount] = useState(0);
const lastRenderTime = new Date().toString();
function bodyFn() {
alert("bodyFn: " + lastRenderTime);
}
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={bodyFn} />
</>
);
}
每當<App>
組件重新渲染時(例如,如果修改了count
state),就會創建一個新的bodyFn
。
如果<SomeChildComponent>
監視它的onClick
引用(通常在依賴數組中),它將每次都看到新的創建。
但是事件回調行為是預期的:每當bodyFn
時,它都是 function 的“最新創建”,特別是它正確使用了lastRenderTime
的最新值(與已經顯示的相同)。
useCallback
的常見用法function App() {
const lastRenderTime = new Date().toString();
const ucbFn = useCallback(
() => alert("ucbFn: " + lastRenderTime),
[lastRenderTime]
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={ucbFn} />
</>
);
}
每當<App>
重新渲染時, lastRenderTime
值是不同的。 因此useCallback
依賴數組啟動,為 ucbFn 創建一個新的更新ucbFn
。
與前一種情況一樣, <SomeChildComponent>
每次都會看到這種變化。 而且useCallback
的使用似乎毫無意義!
但至少,行為也符合預期:function 始終是最新的,並顯示正確的lastRenderTime
值。
useCallback
依賴function App() {
const lastRenderTime = new Date().toString();
const ucbFnNoDeps = useCallback(
() => alert("ucbFnNoDeps: " + lastRenderTime),
[] // Attempt to avoid cb modification by emptying the dependency array
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={ucbFnNoDeps} />
</>
);
}
在恢復useCallback
優勢的“天真”嘗試中,可能會試圖從其依賴數組中刪除lastRenderTime
。
現在ucbFnNoDeps
確實總是一樣的,並且<SomeChildComponent>
將看不到任何變化。
但是現在,行為不再像人們所期望的那樣: ucbFnNoDeps
讀取 lastRenderTime 的值,該值在其創建時的 scope 中lastRenderTime
即第一次呈現<App>
!
useEventCallback
鈎子function App() {
const lastRenderTime = new Date().toString();
const uecbFn = useEventCallback(
() => alert("uecbFn: " + lastRenderTime),
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={uecbFn} />
</>
);
}
每當<App>
重新渲染時,都會創建一個新箭頭 function( useEventCallback
自定義掛鈎的參數)。 但是鈎子內部只是將它存儲在它的useRef
當前占位符中。
鈎子返回 function,在uecbFn
中,永遠不會改變。 所以<SomeChildComponent>
看不到任何變化。
但恢復了最初預期的行為:回調執行時,會查找當前占位符內容,即最近創建的箭頭 function。 因此使用最新的lastRenderTime
值!
<SomeChildComponent>
示例依賴於其回調道具之一的引用的組件示例可能是:
function SomeChildComponent({
onClick,
}: {
onClick: () => void;
}) {
const countRef = useRef(0);
useEffect(
() => { countRef.current += 1; },
[onClick] // Increment count when onClick reference changes
);
return (
<div>
onClick changed {countRef.current} time(s)
<button onClick={onClick}>click</button>
</div>
)
}
CodeSandbox 上的演示: https://codesandbox.io/s/nice-dubinsky-qjp3gk
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.