简体   繁体   English

为什么在使用 useCallback 时使用 'ref' 而不是直接使用 useCallback

[英]why use 'ref' when you use useCallback instead of using useCallback directly

I'm working on React project, and I was investigating some libraries.我正在研究 React 项目,并且正在调查一些库。 and I found they used 'useCallback" differently from what I've used. Below is that code part. I still think this code has no difference from using "useCallback" in a direct way though我发现他们使用的“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), []);
}

so my question is that what does mean by 'useCallback hell'?所以我的问题是'useCallback hell'是什么意思? and what is the advantage of using "useCallback" such that way?以这种方式使用“useCallback”有什么好处?

// BTW: I found a similar example on react documentation. // 顺便说一句:我在反应文档中找到了一个类似的示例。 but I still couldn't understand https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback但我仍然无法理解https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-chang-value-from-usecallback

When you do a normal useCallback , you must pass in a dependency array containing the variables your function uses.当您执行正常的useCallback时,您必须传入一个依赖数组,其中包含 function 使用的变量。 When one of them changes, the memoization breaks.当其中一个发生变化时,记忆就会中断。 This is fine in many cases, but sometimes your memoization is breaking all the time (because you depend on values that change all the time).在许多情况下这很好,但有时你的记忆总是在中断(因为你依赖于一直在变化的值)。 When this happens, useCallback provides little or no benefit.发生这种情况时, useCallback提供的好处很少或根本没有。

The code you've shown has the goal that the memoization never breaks, even if you have complicated dependencies.您展示的代码的目标是记忆化永不中断,即使您有复杂的依赖关系。 Note that when it calls useCallback , it passes in an empty dependency array [] .请注意,当它调用useCallback时,它传入一个空的依赖数组[] That's combined with using a ref to be able to keep track of what the latest handler is.结合使用 ref 能够跟踪最新的handler是什么。 Then when the function is eventually called, it will check the ref for the latest handler and call it.然后当最终调用 function 时,它将检查 ref 以获取最新的handler并调用它。 That latest handler has the latest values in its closure, so it behaves as expected.最新的handler在其闭包中具有最新的值,因此它的行为符合预期。

This code does achieve its goal of never breaking memoization.这段代码确实实现了永不破坏记忆的目标。 However, it needs to be used carefully.但是,需要谨慎使用。 If you are using react's concurrent rendering, and you call the function returned by useEventCallback during rendering, you can get some unexpected results.如果你使用react的并发渲染,在渲染过程中调用useEventCallback返回的useEventCallback ,会得到一些意想不到的结果。 It's only safe to call the function outside of rendering, such as in an event callback, which is why they named it useEventCallback .只有在渲染之外调用 function 是安全的,例如在事件回调中,这就是他们将其命名为useEventCallback的原因。

Explanations with some examples:举例说明:

Function in component body 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} />
    </>
  );
}

Whenever the <App> component re-renders (eg if the count state is modified), a new bodyFn is created.每当<App>组件重新渲染时(例如,如果修改了count state),就会创建一个新的bodyFn

Should <SomeChildComponent> monitor its onClick prop reference (typically in a dependency array), it will see that new creation everytime.如果<SomeChildComponent>监视它的onClick引用(通常在依赖数组中),它将每次都看到新的创建。

But the event callback behaviour is as expected: whenever bodyFn is called, it is the "most recent creation" of that function, and in particular it correctly uses the latest value of lastRenderTime (the same as already displayed).但是事件回调行为是预期的:每当bodyFn时,它都是 function 的“最新创建”,特别是它正确使用了lastRenderTime的最新值(与已经显示的相同)。

Common usage of useCallback useCallback的常见用法

function App() {
  const lastRenderTime = new Date().toString();
  
  const ucbFn = useCallback(
    () => alert("ucbFn: " + lastRenderTime),
    [lastRenderTime]
  );

  return (
    <>
      Last render time: {lastRenderTime}
      <SomeChildComponent onClick={ucbFn} />
    </>
  );
}

Whenever the <App> re-renders, the lastRenderTime value is different.每当<App>重新渲染时, lastRenderTime值是不同的。 Hence the useCallback dependency array kicks in, creating a new updated function for ucbFn .因此useCallback依赖数组启动,为 ucbFn 创建一个新的更新ucbFn

As in the previous case, <SomeChildComponent> will see that change everytime.与前一种情况一样, <SomeChildComponent>每次都会看到这种变化。 And the usage of useCallback seems pointless!而且useCallback的使用似乎毫无意义!

But at least, the behaviour is also as expected: the function is always up-to-date, and shows the correct lastRenderTime value.但至少,行为也符合预期:function 始终是最新的,并显示正确的lastRenderTime值。

Attempt to avoid useCallback dependency尝试避免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} />
    </>
  );
}

In a "naive" attempt to restore the advantage of useCallback , one may be tempted to remove lastRenderTime from its dependency array.在恢复useCallback优势的“天真”尝试中,可能会试图从其依赖数组中删除lastRenderTime

Now ucbFnNoDeps is indeed always the same, and <SomeChildComponent> will see no change.现在ucbFnNoDeps确实总是一样的,并且<SomeChildComponent>将看不到任何变化。

But now, the behaviour is no longer as one could expect: ucbFnNoDeps reads the value of lastRenderTime that was in its scope when it was created , ie the first time <App> was rendered!但是现在,行为不再像人们所期望的那样: ucbFnNoDeps读取 lastRenderTime 的值,该值在其创建时的 scope 中lastRenderTime一次呈现<App>

With custom useEventCallback hook使用自定义useEventCallback钩子

function App() {
  const lastRenderTime = new Date().toString();
  
  const uecbFn = useEventCallback(
    () => alert("uecbFn: " + lastRenderTime),
  );

  return (
    <>
      Last render time: {lastRenderTime}
      <SomeChildComponent onClick={uecbFn} />
    </>
  );
}

Whenever the <App> re-renders, a new arrow function (argument of useEventCallback custom hook) is created.每当<App>重新渲染时,都会创建一个新箭头 function( useEventCallback自定义挂钩的参数)。 But the hook internally just stores it in its useRef current placeholder.但是钩子内部只是将它存储在它的useRef当前占位符中。

The hook returned function, in uecbFn , never changes.钩子返回 function,在uecbFn中,永远不会改变。 So <SomeChildComponent> sees no change.所以<SomeChildComponent>看不到任何变化。

But the initially expected behaviour is restored: when the callback is executed, it will look for the current placeholder content, which is the most recently created arrow function.但恢复了最初预期的行为:回调执行时,会查找当前占位符内容,即最近创建的箭头 function。 Which therefore uses the most recent lastRenderTime value!因此使用最新的lastRenderTime值!

Example of <SomeChildComponent> <SomeChildComponent>示例

An example of a component that depends on the reference of one of its callback props could be:依赖于其回调道具之一的引用的组件示例可能是:

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>
  )
}

Demo on CodeSandbox: https://codesandbox.io/s/nice-dubinsky-qjp3gk CodeSandbox 上的演示: https://codesandbox.io/s/nice-dubinsky-qjp3gk

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM