为什么不能`useCallback` 总是返回相同的引用

时间:2021-01-25 18:20:22

标签: javascript reactjs react-hooks usecallback

我不明白为什么每次更新一个 deps 时 useCallback 总是返回一个新的 ref。这会导致 React.memo() 本来可以避免的许多重新渲染。

这个 useCallback 的实现有什么问题(如果有的话)?

export function useCallback(callback) {

    const callbackRef = useRef();

    callbackRef.current = callback;

    return useState(() =>
        (...args) => callbackRef.current(...args)
    )[0];

}

使用它而不是内置实现肯定会对性能产生显着的积极影响。

自己的结论:

没有理由不使用 ref 而不是内置的,只要您知道其中的含义,即,正如@Bergy 所指出的,您不能存储一个回调以供稍后使用(例如在 setTimeout 之后),并期望回调具有与同步调用相同的效果。
然而,在我看来,这是首选的行为,所以没有缺点?。

2 个答案:

答案 0 :(得分:5)

<块引用>

这个 useCallback 的实现有什么问题(如果有的话)?

我怀疑当有人存储对您的回调的引用供以后使用时会产生意想不到的后果,因为它会改变它正在做的事情:

const { Fragment, useCallback, useState } = React;

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

function Example({printer}) {
  const [printerHistory, setHistory] = useState([]);
  return <Fragment>
    <ul>{
      printerHistory.map(printer => <li>{printer()}</li>)
    }</ul>
    <button onClick={e => setHistory([...printerHistory, printer])}>Store</button>
  </Fragment>
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://unpkg.com/react@16.14.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

(当然,在这个简化的演示中,printer 回调只不过是对 value 本身的一个无用的闭包,但是您可以想象一个更复杂的情况,您可以选择单个历史记录条目,并希望在回调中使用复杂的按需计算)

对于原生 useCallback,存储在 printerHistory 中的函数将是不同值上的不同闭包,而在您的实现中,它们都是引用最新 {{1} } 参数,并且只在每次调用时打印当前值。

答案 1 :(得分:0)

useCallback 和 useMemo 的用例是不同的。 您说 useCallback 返回回调的记忆版本,该版本仅在依赖项之一发生更改时才会更改。因此,它会根据依赖项的变化重新渲染组件。 但是 useMemo 只保存可变值。 通常,我们使用 useCallback 来处理事件处理程序。 例如,假设当您单击按钮时,会显示按钮单击次数。 在这种情况下,点击事件是使用 useCallback 实现的。

 const [count, setCount] = useState(0);
 const handleClick = useCallback(() => {
   setCount(count + 1);
   console.log(count + 1);
 }, [count]);

要获得新增加的计数值并在单击按钮时显示其值,需要重新渲染。 再说一遍,useCallback 和 useMemo 的用例是根据用途来的。

相关问题