1. 将函数作为属性传递给子组件
这是使用 useCallback
最常见且最重要的场景。
当父组件重新渲染时,它内部声明的所有函数都会被重新创建。如果你将这些新创建的函数作为 props
传递给子组件,即使函数的功能没有改变,子组件也会因为接收到新的 prop
引用而重新渲染。
这对于使用了 React.memo
的子组件尤其重要。React.memo
旨在通过浅层比较 props
来防止不必要的重新渲染。然而,如果传递的函数 prop
每次都是一个新的引用,React.memo
的优化就会失效,因为它会认为 props
已经改变了。
useCallback
能够确保在它的依赖项没有变化时,函数引用保持不变,从而让 React.memo
正常工作。
javascript
import React, { useState, useCallback } from 'react';
// 使用 React.memo 包装子组件,以防止不必要的重新渲染
const ButtonComponent = React.memo(({ onClick }) => {
console.log('按钮组件已渲染');
return <button onClick={onClick}>点击我</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// 不使用 useCallback 的情况:
// const handleClick = () => setCount(count + 1);
// 使用 useCallback 缓存函数,只有在依赖项([])改变时才重新创建。
// 由于依赖项为空,此函数只在组件首次渲染时创建一次。
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
console.log('父组件已渲染');
return (
<div>
<p>计数: {count}</p>
{/* 将缓存的函数传递给子组件 */}
<ButtonComponent onClick={handleClick} />
</div>
);
}
2. 作为其他Hook的依赖项
如果一个函数被用作 useEffect
、useMemo
或另一个 useCallback
的依赖项,那么你也应该使用 useCallback
。
场景:作为 useEffect
的依赖项
如果不使用 useCallback
,每次父组件渲染时,这个函数都会被重新创建,导致 useEffect
认为它的依赖项发生了变化,从而重复执行副作用(例如,重复发起网络请求)。
javascript
import React, { useState, useEffect, useCallback } from 'react';
function MyDataFetcher() {
const [data, setData] = useState(null);
// 使用 useCallback 确保 fetchData 的引用是稳定的
const fetchData = useCallback(async () => {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error("数据获取失败", error);
}
}, []); // 由于依赖项为空,该函数在组件生命周期内只创建一次
useEffect(() => {
// 依赖项是 fetchData 函数,因此它的引用必须保持稳定
fetchData();
}, [fetchData]); // 如果没有 useCallback,这里会造成无限循环
return <div>{data ? JSON.stringify(data) : '正在加载...'}</div>;
}
以上代码,可以如何优化,useEffect的第二个参数为空数组不行吗?
结论
useCallback
的核心价值在于 :在依赖项不改变的前提下,保持函数引用的稳定性。
你不需要在每个函数上都使用 useCallback
。只有当你遇到以下情况时,才应该考虑使用它:
- 将函数作为
props
传递给一个使用React.memo
优化的子组件。 - 将函数作为
useEffect
或useMemo
的依赖项。
在大多数简单组件中,重新创建函数引用的开销非常小,并不会对性能造成明显影响。过度使用 useCallback
反而会增加代码的复杂性。因此,请有针对性地使用它,将其视为一个性能优化工具,而非常规开发模式。