何时使用 useCallback - ReactJS?
这篇文章没有“是/否”的答案,但试图从初学者的角度解释为什么这个钩子是官方名册的一部分。
让我们从两个部分开始讲这个故事:
- 父母
- 孩子
父组件有一个按钮,可以增加同一组件中的计数状态,而子组件与此无关。
点击“重新渲染”时,请注意控制台日志。子组件和父组件都会重新渲染并显示以下日志:
re-render parent component
re-render child component.
即使子组件与状态完全无关。
现在,我们必须阻止子组件重新渲染。保留函数式组件,我们可以使用React.memo来实现这一点。子组件将变为:
import React, { memo } from "react";
const Child = memo(({ reset }) => {
// same content as earlier
});
如果没有第二个参数,memo
将对 props 进行浅层比较:
if(prevProps !== props) {
rerender();
} else {
// don't
}
现在您可以检查日志,发现它在父组件重新渲染时不会更新子组件。它只会使用以下日志更新父组件:
re-render parent component
现在,需求已经取得进展,我们必须在Child
组件内部放置一个用于计数的重置按钮。
这将使孩子折射到:
import React, { memo } from "react";
const Child = memo(({ reset }) => {
console.log("re-render child component.")
return (
<div>
<p>child component which resets count</p>
<button onClick={reset}>Reset Count</button>
</div>
);
});
export default Child;
对于重置功能,我们必须将父级折射为:
const Parent () => {
const [count, setCount] = useState(0);
console.log("re-render parent component");
const resetCount = () => {
setCount(0);
};
return (
<main>
<p>Count: {count}</p>
<button onClick={() => setCount(count=>(count+1))}>Increment</button>
<Child reset={resetCount} />
</main>
)
}
现在,您可以点击重置按钮将计数重置为 0。但是您会注意到,memo
我们之前应用的魔法不再起作用。日志显示子组件和父组件都进行了重新渲染。
为什么会发生这种情况?
正如我们之前提到的,依赖于和memo
的引用相等性才能工作。但是该函数在和的每次渲染中都会被创建,因此和不再相同(即使它们本来就相同)。prevProps
props
resetCount
Parent
prevProps
props
现在,为了再次运用memo
魔法,我们需要确保resetCount
函数在每次渲染时不会不必要地重新创建Parent
。这正是useCallback
我们所需要的。
const resetCount = useCallback(() => {
setCount(0);
}, [setCount]);
useCallback
重新渲染时始终返回相同的函数实例,并且仅当依赖项发生变化时才会刷新。请注意 的第二个参数useCallback
,它与钩子非常相似useEffect
,指的是应该触发钩子内函数重新初始化的依赖项useCallback
。
完成的演示:
从我的博客转载
延伸阅读:
- useCallback 文档
- 何时使用 useMemo 和 useCallback - Kent C Dodds
- 如何从 useCallback 中读取经常变化的值?
- 由于在渲染中创建函数,Hooks 是否很慢?