什么是 useCallback
Hook?
useCallback
是一个 React Hook,用于缓存函数,防止函数在每次组件渲染时被重新创建。它的主要目的是优化性能,特别是在将函数作为 props 传递给子组件或在依赖数组中使用时。
简单来说,useCallback
让你的函数“记住”之前的定义,只有当依赖项变化时才重新生成函数。
为什么需要 useCallback
?
在 React 中,函数组件每次渲染时,函数体都会重新执行,内部定义的函数也会被重新创建。新创建的函数引用不同,即使函数逻辑没变。这可能导致:
- 子组件不必要的重新渲染:如果函数作为 props 传递给子组件,而子组件使用了
React.memo
,新函数引用会导致子组件重新渲染。 - 依赖数组问题:如果函数被用在
useEffect
或useMemo
的依赖数组中,每次渲染生成的新函数会导致副作用或计算重复执行。
useCallback
解决了这些问题,通过缓存函数确保引用稳定。
useCallback
的基本语法
useCallback
接收两个参数:
- 回调函数:你要缓存的函数。
- 依赖数组:控制函数何时重新生成,只有依赖项变化时函数才会更新。
基本结构:
import { useCallback } from 'react';function MyComponent() {const memoizedFunction = useCallback(() => {// 函数逻辑}, [/* 依赖项 */]);return <div>...</div>;
}
- 返回值:
useCallback
返回一个缓存的函数,在依赖项不变的情况下,函数引用保持一致。
useCallback
的使用场景
以下通过具体例子,带你理解 useCallback
的实际用途。
1. 优化子组件渲染(搭配 React.memo
)
如果子组件使用了 React.memo
,它会根据 props 的引用是否变化来决定是否重新渲染。如果父组件的函数每次渲染都重新创建,子组件会不必要地重新渲染。
示例代码:
import { useState, useCallback } from 'react';
import { memo } from 'react';// 子组件使用 React.memo
const Button = memo(({ handleClick }) => {console.log('Button 渲染了');return <button onClick={handleClick}>点击我</button>;
});function ParentComponent() {const [count, setCount] = useState(0);// 使用 useCallback 缓存函数const handleClick = useCallback(() => {console.log('按钮被点击');}, []); // 空数组,函数永不重新生成return (<div><p>计数: {count}</p><button onClick={() => setCount(count + 1)}>加1</button><Button handleClick={handleClick} /></div>);
}
运行效果:
- 点击“加1”按钮,
count
变化,ParentComponent
重新渲染。 - 但
Button
组件不会重新渲染,因为handleClick
的引用没变(被useCallback
缓存)。 - 如果不用
useCallback
,handleClick
每次渲染都是新函数,导致Button
每次都重新渲染。
关键点:
React.memo
让Button
只在 props 变化时渲染。useCallback
确保handleClick
引用稳定,避免不必要的渲染。
2. 在 useEffect
中使用函数
如果函数被用在 useEffect
的依赖数组中,每次渲染生成新函数会导致 useEffect
重复运行。useCallback
可以缓存函数,防止这种情况。
示例代码:
import { useState, useEffect, useCallback } from 'react';function DataFetcher({ id }) {const [data, setData] = useState(null);// 缓存 fetchData 函数const fetchData = useCallback(() => {fetch(`https://api.example.com/data/${id}`).then(res => res.json()).then(result => setData(result));}, [id]); // 依赖 id,只有 id 变化时重新生成useEffect(() => {fetchData();}, [fetchData]); // 依赖 fetchDatareturn <div>{data ? data.name : '加载中...'}</div>;
}
运行效果:
fetchData
被useCallback
缓存,只有当id
变化时才重新生成。useEffect
依赖fetchData
,因为fetchData
引用稳定,useEffect
不会因为组件渲染而重复运行。- 如果不用
useCallback
,fetchData
每次渲染都是新函数,导致useEffect
每次渲染都运行,触发不必要的 API 请求。
useCallback
vs. 其他 Hooks
-
和
useMemo
的区别:useCallback
缓存函数,返回函数引用。useMemo
缓存值(可以是函数执行的结果)。- 两者功能类似,
useCallback(fn, deps)
等价于useMemo(() => fn, deps)
。
// useCallback const memoizedFn = useCallback(() => doSomething(a, b), [a, b]);// 等价的 useMemo const memoizedFn = useMemo(() => () => doSomething(a, b), [a, b]);
-
和
useEffect
的关系:useCallback
常用于缓存useEffect
依赖的函数,避免不必要的副作用触发。
注意事项(新手常见坑)
-
不要滥用
useCallback
useCallback
本身有内存开销(缓存函数),只在需要优化性能时使用,比如:- 函数作为 props 传递给
React.memo
包裹的子组件。 - 函数作为
useEffect
或useMemo
的依赖。
- 函数作为 props 传递给
- 如果函数不会触发不必要渲染或副作用,直接定义普通函数即可。
-
依赖数组要正确填写
- 确保依赖数组包含函数中用到的所有变量,否则可能导致 bug。
- ESLint 的
react-hooks/exhaustive-deps
规则会帮你检查。
const handleClick = useCallback(() => {console.log(count); // count 必须在依赖数组中 }, [count]);
-
性能优化不是万能的
useCallback
不能解决所有性能问题。如果组件渲染频繁,检查是否可以通过减少状态更新或优化组件逻辑来解决。
总结
useCallback
的作用:缓存函数,防止每次渲染生成新函数引用,优化性能。- 使用场景:
- 配合
React.memo
避免子组件不必要渲染。 - 在
useEffect
或useMemo
中缓存函数,避免重复运行。
- 配合
- 语法:
useCallback(fn, deps)
,返回缓存的函数,依赖项变化时更新。 - 注意事项:只在需要时使用,正确声明依赖数组。