簡體   English   中英

為什么在使用 useCallback 時使用 'ref' 而不是直接使用 useCallback

[英]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 在組件體內

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM