具有依赖项的 useCallback 与使用 ref 调用函数的最后一个版本

时间:2021-01-20 18:51:42

标签: reactjs use-ref usecallback

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

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 永远不会改变,即使其中一个依赖项发生变化。

我不知道如何看待这段代码。我不明白使用 useLastVersionuseCallback 相比有什么缺点。

2 个答案:

答案 0 :(得分:0)

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

有趣的部分是:

<块引用>

另请注意,此模式可能会导致并发模式出现问题。我们计划在未来提供更多符合人体工程学的替代方案,但目前最安全的解决方案是,如果某个值取决于更改,则始终使回调无效。

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

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

答案 1 :(得分:0)

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

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

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

  • Ref + useCallback(hook):在Component中渲染(由于value)+在hook中渲染(useEffect),总共2次。
  • useCallback only:在 Component 中渲染(由于 value)+ 在 Counter 中渲染(由于 value 更改导致函数引用更改),总共 2.< /li>
  • 普通函数:在 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);

Edit Use Last Version


附带说明,如果目的只是找到具有最少渲染次数的 input 的长度,阅读 inputRef.current.value 将是解决方案。