为什么 `useCallback` 不能总是 return 相同的引用
Why can't `useCallback` always return the same ref
我不明白为什么每次更新其中一个部门时 useCallback
总是 returns 一个新的 ref。它导致 React.memo()
本可以避免的许多重新渲染。
useCallback
的实施有什么问题(如果有的话)?
export function useCallback(callback) {
const callbackRef = useRef();
callbackRef.current = callback;
return useState(() =>
(...args) => callbackRef.current(...args)
)[0];
}
使用它而不是内置实现肯定会对性能产生显着的积极影响。
自己的结论:
没有理由不在内置的 上使用 ref 的实现,只要你知道其中的含义,也就是说,正如@Bergy 所指出的,你不能存储回调供以后使用(例如在 setTimeout
之后)并期望回调具有与同步调用它相同的效果。
然而,在我看来,这是首选行为,因此没有缺点。
更新:
有一个 React RFC 用于引入一个内置的钩子来做这件事。它将被称为 useEvent
What is, if any, the problem with this implementation of 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
本身的无用闭包,但您可以想象一个更复杂的情况,其中可以select 一个单独的历史条目,并希望在回调中使用复杂的按需计算)
对于原生 useCallback
,存储在 printerHistory
中的函数将是对不同值的不同闭包,而对于您的实现,它们都是引用最新 useCallback
参数并且只在每次调用时打印当前值。
useCallback 和useMemo 的用例不同。
你说 useCallback returns 一个记忆版本的回调,只有当其中一个依赖项发生变化时才会发生变化。结果,它根据依赖关系的变化重新渲染组件。
但是 useMemo 只持有可变的价值。
通常,我们使用 useCallback 来处理事件处理程序。
例如,假设当您单击按钮时,显示按钮单击次数。
在本例中,点击事件是使用useCallback实现的。
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
console.log(count + 1);
}, [count]);
要获取新的增加计数值并在单击按钮时显示其值,需要重新渲染。
再说一遍,useCallback 和 useMemo 的用例要看用途。
我不明白为什么每次更新其中一个部门时 useCallback
总是 returns 一个新的 ref。它导致 React.memo()
本可以避免的许多重新渲染。
useCallback
的实施有什么问题(如果有的话)?
export function useCallback(callback) {
const callbackRef = useRef();
callbackRef.current = callback;
return useState(() =>
(...args) => callbackRef.current(...args)
)[0];
}
使用它而不是内置实现肯定会对性能产生显着的积极影响。
自己的结论:
没有理由不在内置的 上使用 ref 的实现,只要你知道其中的含义,也就是说,正如@Bergy 所指出的,你不能存储回调供以后使用(例如在 setTimeout
之后)并期望回调具有与同步调用它相同的效果。
然而,在我看来,这是首选行为,因此没有缺点。
更新:
有一个 React RFC 用于引入一个内置的钩子来做这件事。它将被称为 useEvent
What is, if any, the problem with this implementation of
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
本身的无用闭包,但您可以想象一个更复杂的情况,其中可以select 一个单独的历史条目,并希望在回调中使用复杂的按需计算)
对于原生 useCallback
,存储在 printerHistory
中的函数将是对不同值的不同闭包,而对于您的实现,它们都是引用最新 useCallback
参数并且只在每次调用时打印当前值。
useCallback 和useMemo 的用例不同。 你说 useCallback returns 一个记忆版本的回调,只有当其中一个依赖项发生变化时才会发生变化。结果,它根据依赖关系的变化重新渲染组件。 但是 useMemo 只持有可变的价值。 通常,我们使用 useCallback 来处理事件处理程序。 例如,假设当您单击按钮时,显示按钮单击次数。 在本例中,点击事件是使用useCallback实现的。
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
console.log(count + 1);
}, [count]);
要获取新的增加计数值并在单击按钮时显示其值,需要重新渲染。 再说一遍,useCallback 和 useMemo 的用例要看用途。