具有依赖性的 useCallback 与使用 ref 调用函数的最新版本

useCallback with dependency vs using a ref to call the last version of the function

在进行代码审查时,我遇到了这个自定义挂钩:

import { useRef, useEffect, useCallback } from 'react'

export default function useLastVersion (func) {
  const ref = useRef()
  useEffect(() => {
    ref.current = func
  }, [func])
  return useCallback((...args) => {
    return ref.current(...args)
  }, [])
}

这个钩子是这样使用的:

const f = useLastVersion(() => { // do stuff and depends on props })

基本上,与 const f = useCallBack(() => { // do stuff }, [dep1, dep2]) 相比,这避免了声明依赖项列表,并且 f 永远不会更改,即使其中一个依赖项发生变化。

我不知道如何看待这段代码。我不明白与 useCallback.

相比,使用 useLastVersion 有什么缺点

这个问题实际上已经或多或少地在文档中得到了回答:https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback

有趣的部分是:

Also note that this pattern might cause problems in the concurrent mode. We plan to provide more ergonomic alternatives in the future, but the safest solution right now is to always invalidate the callback if some value it depends on changes.

阅读也很有趣:https://github.com/facebook/react/issues/14099 and https://github.com/reactjs/rfcs/issues/83

当前的建议是使用提供程序来避免在 props 中传递回调,如果我们担心这会导致过多的重新渲染。

我的观点如评论中所述,当依赖项更改过于频繁时(在 useEffect/useCallback dep 数组),使用普通函数是最好的选择(无开销)。

这个钩子隐藏了使用它的组件的渲染,但渲染来自其父级中的 useEffect

如果我们总结我们得到的渲染计数:

  • Ref + useCallback (the hook): Render in Component (due to value) + Render in hook (useEffect), 共2.
  • useCallback:在 Component 中呈现(由于 value)+ 在 Counter 中呈现(由于 value 更改而导致的函数引用更改),总共 2 个.
  • 普通函数:在 Component 中渲染 + 在 Counter 中渲染:每次渲染都有新函数,总共 2 个。

但是在 useEffectuseCallback.

中进行浅比较会产生额外的开销

实际例子:

function App() {
  const [value, setValue] = useState("");
  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        type="text"
      />
      <Component value={value} />
    </div>
  );
}

function useLastVersion(func) {
  const ref = useRef();
  useEffect(() => {
    ref.current = func;
    console.log("useEffect called in ref+callback");
  }, [func]);
  return useCallback((...args) => {
    return ref.current(...args);
  }, []);
}

function Component({ value }) {
  const f1 = useLastVersion(() => {
    alert(value.length);
  });

  const f2 = useCallback(() => {
    alert(value.length);
  }, [value]);

  const f3 = () => {
    alert(value.length);
  };

  return (
    <div>
      Ref and useCallback:{" "}
      <MemoCounter callBack={f1} msg="ref and useCallback" />
      Callback only: <MemoCounter callBack={f2} msg="callback only" />
      Normal: <MemoCounter callBack={f3} msg="normal" />
    </div>
  );
}

function Counter({ callBack, msg }) {
  console.log(msg);
  return <button onClick={callBack}>Click Me</button>;
}

const MemoCounter = React.memo(Counter);


作为旁注,如果目的只是找到具有最小渲染的 input 的长度,阅读 inputRef.current.value 将是解决方案。