React useCallback介绍(用来缓存函数的引用,避免每次渲染都重新创建函数)主要用于性能优化
文章目录
- 🚀 React `useCallback` 深入理解与实战应用
- 🌱 一、`useCallback` 是什么?
- `useCallback` 是 React 提供的一个 Hook,用来**缓存函数的引用**。
- 语法
- 简而言之: **`useCallback` 返回一个记忆化的函数版本,只有当依赖改变时才会重新生成。**
- ⚡ 二、为什么需要 `useCallback`
- 📉 示例:未使用 `useCallback` 的问题
- 代码
- 分析
- ⚙️ 三、使用 `useCallback` 解决
- 代码
- 分析
- 🔍 四、`useCallback` 与 `useMemo` 的区别
- 🧠 五、使用时机与注意事项
- ✅ 适合使用的场景
- 1. 子组件使用了 `React.memo`,并且接收函数作为 `props`;
- 2. 函数在依赖变化前后逻辑相同;
- 3. 函数的重新创建会导致不必要的渲染或副作用。
- ⚠️ 不建议滥用
- * `useCallback` 本身也有性能开销;
- * 对普通函数(非 props 传递、不影响渲染)没必要使用;
- * 若滥用,会增加代码复杂度且收益有限。
- 🔧 建议
- 💡 六、进阶技巧:搭配自定义 Hook 使用
- 🧩 七、总结
- 🎯 结语
🚀 React useCallback
深入理解与实战应用
在 React 开发中,性能优化始终是一个值得关注的话题。很多开发者在使用 Hooks 时都会遇到这样的疑问:
“为什么我需要用
useCallback
?它到底解决了什么问题?什么时候该用它?”
本文将通过原理讲解 + 示例对比 + 实战技巧,帮助你彻底理解 React 的 useCallback
。
🌱 一、useCallback
是什么?
useCallback
是 React 提供的一个 Hook,用来缓存函数的引用。
语法
const memoizedCallback = useCallback(() => {// 回调函数逻辑},[dep1, dep2]
);
- 第一个参数:需要缓存的回调函数。
- 第二个参数:依赖数组(当依赖变化时,才会重新创建函数)。
简而言之: useCallback
返回一个记忆化的函数版本,只有当依赖改变时才会重新生成。
⚡ 二、为什么需要 useCallback
在 React 中,每次组件重新渲染,所有内部定义的函数都会被重新创建。
这在大多数场景下没问题,但在某些情况下会带来性能问题。
📉 示例:未使用 useCallback
的问题
代码
import React, { useState } from "react";"use client";import React, { useState } from "react";function Child({ onClick }: { onClick: () => void }) {console.log("🔄 Child render");return <button onClick={onClick}>点击子组件按钮</button>;
}const MemoChild = React.memo(Child);function App() {const [count, setCount] = useState(0);const handleClick = () => {console.log("Clicked");};return (<div><p>Count: {count}</p><div><button onClick={() => setCount(c => c + 1)}>加一</button></div><div><MemoChild onClick={handleClick} /></div></div>);
}export default App;
分析
运行这段代码后你会发现:
👉 即使我们只更新了 count
,MemoChild
仍然会重新渲染!
原因是:
- 每次
App
渲染都会重新创建一个新的handleClick
函数(不同内存地址); - 即使函数内容相同,但它们的引用不同;
React.memo
判断props
变化时发现onClick
是一个新引用,于是重新渲染了子组件。
⚙️ 三、使用 useCallback
解决
代码
我们可以用 useCallback
来缓存这个函数引用:
const handleClick = useCallback(() => {console.log("Clicked");
}, []);
完整代码如下:
"use client";import React, { useState, useCallback } from "react";function Child({ onClick }: { onClick: () => void }) {console.log("🔄 Child render");return <button onClick={onClick}>点击子组件按钮</button>;
}const MemoChild = React.memo(Child);function App() {const [count, setCount] = useState(0);const handleClick = useCallback(() => {console.log("Clicked");}, []);return (<div><p>Count: {count}</p><div><button onClick={() => setCount(c => c + 1)}>加一</button></div><div><MemoChild onClick={handleClick} /></div></div>);
}export default App;
分析
✅ 现在,当我们点击“加一”按钮时:
- 父组件会重新渲染;
- 但
handleClick
的引用未变; - 子组件
MemoChild
不再重新渲染。
性能优化达成 ✅
🔍 四、useCallback
与 useMemo
的区别
Hook | 返回值 | 主要用途 |
---|---|---|
useCallback(fn, deps) | 返回函数 | 缓存回调函数引用 |
useMemo(factory, deps) | 返回值 | 缓存计算结果 |
其实:
useCallback(fn, deps) ≡ useMemo(() => fn, deps)
但语义上更清晰:
- 当你缓存的是函数,用
useCallback
; - 当你缓存的是计算结果,用
useMemo
。
🧠 五、使用时机与注意事项
✅ 适合使用的场景
1. 子组件使用了 React.memo
,并且接收函数作为 props
;
2. 函数在依赖变化前后逻辑相同;
3. 函数的重新创建会导致不必要的渲染或副作用。
⚠️ 不建议滥用
* useCallback
本身也有性能开销;
* 对普通函数(非 props 传递、不影响渲染)没必要使用;
* 若滥用,会增加代码复杂度且收益有限。
🔧 建议
先写出正确的代码,再通过性能分析(如 React DevTools Profiler)决定是否引入
useCallback
。
💡 六、进阶技巧:搭配自定义 Hook 使用
在自定义 Hook 中使用 useCallback
,可以有效避免无限循环:
function useFetch(url) {const [data, setData] = useState(null);const fetchData = useCallback(async () => {const res = await fetch(url);const json = await res.json();setData(json);}, [url]);useEffect(() => {fetchData();}, [fetchData]);return data;
}
这里若不加 useCallback
,fetchData
每次都会变,useEffect
就会无限执行。
在这个代码中,fetchData 会因为 url 变化而变化。具体来说:
- useCallback 的依赖项是 [url],所以当 url 发生变化时,fetchData 函数会重新创建
- useEffect 的依赖项是 [fetchData],所以当 fetchData 变化时,useEffect 会重新执行
- 当 useEffect 重新执行时,它会调用新的 fetchData 函数,从而触发新的 API 请求
🧩 七、总结
项目 | 说明 |
---|---|
作用 | 缓存函数引用,避免子组件重复渲染 |
语法 | useCallback(fn, deps) |
适用场景 | 函数作为 props 传递给子组件时 |
常见搭配 | React.memo 、useEffect |
不要滥用 | 若无性能瓶颈,使用纯函数更简单 |
🎯 结语
useCallback
并不是一个“必须用”的 Hook,而是在特定性能场景下的优化工具。
理解它的核心思路——函数引用的稳定性,才能在正确的地方使用它。