
React 学习笔记:Callback Hook
发布于: 2026-06-09 22:56:28 更新于: 2026-06-09 22:56:28
useCallback 基础
什么是 useCallback?
useCallback 是 React 提供的性能优化 Hook,用于缓存函数定义。它的核心思想是:
如果依赖项没有变化,就直接返回上次的函数引用,避免重复创建新函数。
简单来说,useMemo 缓存的是计算结果(值),而 useCallback 缓存的是函数本身。
基本用法
import { useCallback } from "react";
function ProductPage({ productId, referrer }) {
// 缓存函数定义,只有 productId 或 referrer 变化时才创建新函数
const handleSubmit = useCallback(
(orderDetails) => {
post("/product/" + productId + "/buy", {
referrer,
orderDetails,
});
},
[productId, referrer],
); // ✅ 依赖数组
}
参数说明
useCallback(fn, dependencies) 接收两个参数:
| 参数 | 说明 |
|---|---|
| fn | 要缓存的函数,可以是任意函数(普通函数、箭头函数) |
| dependencies | 依赖数组,包含函数内部引用的所有响应式值(props、state、组件内变量) |
返回值
- 首次渲染:返回传入的
fn函数 - 后续渲染:检查依赖项是否变化
- 未变化:返回同一个函数引用(跳过创建新函数)
- 已变化:返回新传入的
fn函数,并更新缓存
什么时候使用
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// ↑
// 只有 a 或 b 变化时,memoizedFn 才会是新的引用
关键点:React 不会调用你的函数,只是返回给你,由你决定何时调用。
核心使用场景
场景 1:传递给 memo 子组件(最主要用途)
当父组件频繁重新渲染,但子组件用 React.memo 包裹,且子组件接受函数作为 props 时,useCallback 可以避免子组件不必要的重新渲染。
不使用 useCallback(子组件每次都重新渲染):
const Child = React.memo(function Child({ onClick }) {
console.log("Child 渲染了");
return <button onClick={onClick}>点击</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// ❌ 每次 Parent 渲染都创建新的 handleClick 函数
// 即使 Child 用了 React.memo,也会因为 onClick prop 变化而重新渲染
const handleClick = () => {
console.log("clicked");
};
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
);
}
使用 useCallback(子组件跳过不必要的渲染):
const Child = React.memo(function Child({ onClick }) {
console.log("Child 渲染了");
return <button onClick={onClick}>点击</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// ✅ 缓存函数引用,没有依赖项,永远是同一个引用
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
);
}
场景 2:作为 useEffect 的依赖项
当函数需要在 useEffect 中使用,但你不想每次渲染都重新执行 effect 时:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState("");
// ✅ 缓存函数,只有 roomId 变化时才创建新函数
const createOptions = useCallback(
() => ({
serverUrl: "https://localhost:1234",
roomId: roomId,
}),
[roomId],
);
// ✅ 因为 createOptions 引用稳定,effect 只在 roomId 变化时重新执行
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [createOptions]);
return <input value={message} onChange={(e) => setMessage(e.target.value)} />;
}
场景 3:缓存事件处理函数
function TodoList({ todos, onDelete }) {
const [newTodo, setNewTodo] = useState("");
// ✅ 缓存添加待办的处理函数
const handleAdd = useCallback(() => {
if (newTodo.trim()) {
onDelete(newTodo);
setNewTodo("");
}
}, [newTodo, onDelete]);
// ✅ 缓存回车键处理函数
const handleKeyDown = useCallback(
(e) => {
if (e.key === "Enter") {
handleAdd();
}
},
[handleAdd],
);
return (
<div>
<input
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
onKeyDown={handleKeyDown}
/>
<button onClick={handleAdd}>添加</button>
</div>
);
}
useCallback vs useMemo
这是最容易混淆的两个 Hook,它们的关系是:
// useCallback 缓存的是函数本身
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// 等价于
const memoizedFn = useMemo(() => {
return () => {
doSomething(a, b);
};
}, [a, b]);
| Hook | 缓存什么 | 用途 |
|---|---|---|
useCallback |
函数引用 | 缓存函数定义,避免子组件重新渲染 |
useMemo |
计算结果(值) | 缓存昂贵计算的结果 |
简单记忆:
useCallback(fn, deps)≈useMemo(() => fn, deps)
何时使用 useCallback?
✅ 应该使用
| 场景 | 原因 |
|---|---|
传递函数给 React.memo 包裹的子组件 |
避免子组件因函数引用变化而重新渲染 |
函数作为 useEffect 的依赖项 |
避免 effect 不必要的重新执行 |
| 函数作为其他 Hook 的依赖项 | 保持依赖项稳定 |
❌ 不需要使用
| 场景 | 原因 |
|---|---|
| 函数只在当前组件使用 | 没有传递给子组件,缓存没有意义 |
子组件没有用 React.memo |
即使函数引用不变,子组件也会重新渲染 |
| 函数内部依赖频繁变化 | 缓存很快就会失效,反而增加了开销 |
| 简单的事件处理函数 | 创建函数的开销很小,不值得缓存 |
注意事项
1. 只在组件顶层调用
function MyComponent() {
// ✅ 正确:在组件顶层调用
const handleClick = useCallback(() => {}, []);
// ❌ 错误:不能在条件语句或循环中调用
if (condition) {
const handleClick = useCallback(() => {}, []); // 不要这样做!
}
}
2. 依赖数组必须完整
function MyComponent({ onClick, data }) {
// ❌ 错误:缺少依赖 data
const handleSubmit = useCallback(() => {
onClick(data);
}, [onClick]);
// ✅ 正确:包含所有使用的响应式值
const handleSubmit = useCallback(() => {
onClick(data);
}, [onClick, data]);
}
3. 不要滥用 useCallback
function SimpleComponent({ name }) {
// ❌ 不需要:这个函数没有传递给子组件
const getGreeting = useCallback(() => `Hello, ${name}!`, [name]);
// ✅ 直接写即可
const getGreeting = () => `Hello, ${name}!`;
}
记住:useCallback 本身也有开销(存储缓存、比较依赖)。对于不需要缓存的场景,直接创建函数可能更快。
4. 函数内部使用最新的 state
function Counter() {
const [count, setCount] = useState(0);
// ❌ 问题:count 被"困"在闭包中,永远是初始值 0
const handleClick = useCallback(() => {
console.log(count); // 永远是 0
setCount(count + 1); // 永远设置为 1
}, []);
// ✅ 正确:将 count 加入依赖
const handleClick = useCallback(() => {
console.log(count); // 最新的 count
setCount(count + 1);
}, [count]);
// ✅ 更好:使用函数式更新,无需依赖 count
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
}
总结
useCallback 的核心价值:
- 缓存函数引用,避免创建新函数
- 配合
React.memo避免子组件不必要的重新渲染 - 稳定
useEffect等 Hook 的依赖项
使用原则:
- 优先用于传递给 memo 子组件的函数
- 用于作为其他 Hook 依赖项的函数
- 不要滥用,简单函数直接写即可
- 确保依赖数组完整,包含所有使用的响应式值
标签分类
# React# 前端