繁体   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