ahooks 里的 useMemoizedFn 与 useCallback 各自适用场景
ahooks 中的 useMemoizedFn 与 React 原生的 useCallback 都用于缓存函数引用,避免组件重渲染时函数引用变化导致的不必要的副作用或子组件重新渲染。但它们的实现机制和适用场景存在显著差异。
一、useCallback
本质:React 原生 Hook,依赖于依赖项数组来决定是否返回新函数。
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
特点:
- 只有当依赖项
[a, b]变化时,才会返回一个新的函数。 - 如果依赖项频繁变化,函数引用也会频繁变化。
- 依赖闭包中的变量,容易出现“闭包陷阱”(即函数内捕获的是旧值)。
- 只有当依赖项
适用场景:
- 函数依赖少量且稳定的 props 或 state。
- 需要将函数作为 prop 传给
React.memo优化的子组件。 - 用作
useEffect、useMemo等的依赖项,且希望其引用稳定(但必须正确声明依赖)。
局限性:
- 一旦依赖变化,函数就会重新创建。
- 如果忘记更新依赖,可能导致使用过期状态(stale closure)。
二、useMemoizedFn(来自 ahooks)
本质:通过 ref 持续更新函数逻辑,始终返回同一个函数引用,但函数内部总是执行最新版本的逻辑。
const memoizedFn = useMemoizedFn(() => {
doSomething(a, b);
});
- 实现原理(简化):
const fnRef = useRef(fn);
fnRef.current = fn; // 每次渲染都更新 ref 中的函数
const memoizedFn = useCallback((...args) => fnRef.current(...args), []);
- 函数引用永不变化(
useCallback([], ...))。 调用时总是执行最新版本的函数体(通过 ref 获取最新函数)。
- 特点:
引用绝对稳定,不会因依赖变化而改变。
避免闭包陷阱:函数内访问的始终是最新状态(如通过 ref 或最新 props)。
不需要手动管理依赖数组。
- 适用场景:
函数作为事件处理器(如
onClick),需要长期稳定引用(如用于addEventListener、定时器、WebSocket 回调等)。在
useEffect中使用函数,但不想因函数变化而触发副作用重复执行。与
requestAnimationFrame、setTimeout、setInterval等结合使用,避免回调捕获旧值。需要将函数传递给非 React 控制的第三方库(如地图 SDK、Canvas 动画库等)。
- 注意事项:
函数内部若直接使用组件作用域的变量(如
a,b),虽然能拿到最新值(因为每次组件渲染都会更新fnRef.current),但本质上是通过“最新渲染时的函数闭包”实现的,并非神奇地突破了闭包限制。不能替代
useCallback在依赖项稳定时的语义清晰性。
三、对比总结
| 特性 | useCallback | useMemoizedFn |
|------|----------------|------------------|
| 引用稳定性 | 依赖项不变时稳定 | 始终稳定 |
| 是否需要依赖数组 | ✅ 需要 | ❌ 不需要 |
| 是否避免 stale closure | ❌ 容易出现(若依赖未更新) | ✅ 总是执行最新逻辑 |
| 适用场景 | 传给 React.memo 子组件、作为 useEffect 依赖 | 事件监听、定时器、第三方库回调、长期缓存函数 |
| React 原生支持 | ✅ 是 | ❌ 第三方(ahooks) |
| 性能开销 | 低 | 极低(仅一次 useCallback + ref 更新) |
四、使用建议
- 优先使用
useCallback:如果你的函数依赖明确、稳定,且用于 React 渲染优化(如React.memo)。 - 选用
useMemoizedFn:当你需要长期稳定的函数引用,且函数内部逻辑应始终使用最新状态(尤其在副作用或非 React 环境中)。
💡 简单记忆:
-useCallback是“依赖稳定,我就稳定”
-useMemoizedFn是“我永远稳定,但永远执行最新代码”
如仍有具体使用场景不确定,欢迎提供代码示例进一步分析。