hook:useCallback
- 用来记忆并缓存
函数,在指定依赖没有更改时,直接返回缓存的函数,无需重新创建,可用于优化性能 - 核心:
useCallback(fn, deps)等价于useMemo(() => fn, deps)
场景代码示例
-
【场景 1】每次渲染都创建新函数(不建议使用):
-
每次组件更新后,都会创建新的函数,失去了 useCallback 的意义
// 场景:回调函数 - 每次渲染都创建新函数(无意义) const memoizedCallback = useCallback(() => { doSomething(a, b); }); // 这里没有依赖项 -
这样写等同于直接定义
const memoizedCallback = () => { doSomething(a, b); } -
⚠️ 不建议使用,useCallback 的核心价值就是缓存函数引用
-
-
【场景 2】依赖变化时更新函数(常用):
-
依赖项变化时才创建新函数,否则返回缓存的函数
// 场景:事件处理 - 仅 a 或 b 变化时才创建新函数 const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); // 依赖 a 和 b -
只有当
a或b发生变化时,才会创建新的函数 -
其他状态变化导致组件重新渲染时,函数引用保持不变
-
-
【场景 3】仅首次创建函数(常用):
-
只在首次渲染时创建一次函数,后续永远返回同一个函数引用
// 场景:事件监听 - 函数引用永不变化 const handleClick = useCallback(() => { console.log("clicked"); }, []); // 空依赖数组 useEffect(() => { window.addEventListener("click", handleClick); return () => window.removeEventListener("click", handleClick); }, [handleClick]); // handleClick 引用不变,effect 只执行一次 -
适用于不依赖任何外部变量的纯事件处理函数
-
保证函数引用稳定,避免触发不必要的 effect 重新执行
-
-
【场景 4】传递给子组件的回调(常用):
-
避免因父组件重新渲染而导致子组件不必要的重渲染
function Parent() { const [count, setCount] = useState(0); const [other, setOther] = useState(0); // 只在 count 变化时创建新函数 const handleClick = useCallback(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setOther(other + 1)}>更新 other</button> <Child onClick={handleClick} /> {/* other 变化时,Child 不会重渲染 */} </div> ); } // 需要配合 React.memo 使用 const Child = React.memo(({ onClick }) => { console.log("Child rendered"); return <button onClick={onClick}>Click me</button>; }); -
如果不用 useCallback,每次父组件渲染都会创建新的
handleClick -
即使 count 没变,新函数的引用也不同,导致子组件重新渲染
-
注意: 必须配合
React.memo才有效果
-
-
【场景 5】ref 回调函数测量 DOM(常用):
-
使用 ref 回调函数来获取和测量 DOM 节点
function MeasureExample() { const [height, setHeight] = useState(0); // 空依赖:ref 回调函数引用保持不变 const measuredRef = useCallback((node) => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []); // 空数组,函数永不更新 return ( <> <h1 ref={measuredRef}>Hello, world</h1> <h2>The above header is {Math.round(height)}px tall</h2> </> ); } -
ref 回调在节点挂载时会被调用,传入 DOM 节点
-
使用 useCallback 确保 ref 函数引用稳定
-
如果需要响应某些依赖的变化重新测量,可以添加依赖项:
// 当 fontSize 变化时,重新测量高度 const measuredRef = useCallback( (node) => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, [fontSize], ); // 依赖 fontSize
-
依赖数组的区别
| 场景 | 依赖数组 | 函数更新时机 | 使用场景 |
|---|---|---|---|
| 场景 1 | 无 | 每次渲染都创建 | ⚠️ 不建议 |
| 场景 2 | [dep] | 首次 或 依赖变化 | 常用(事件处理) |
| 场景 3 | [] | 仅首次渲染 | 常用(事件监听) |
| 场景 4 | [dep] | 首次 或 依赖变化 | 常用(传递给子组件) |
| 场景 5 | [] | 仅首次渲染 | 常用(ref 回调测量 DOM) |
useCallback vs useMemo
| Hook | 缓存的内容 | 等价写法 | 使用场景 |
|---|---|---|---|
| useCallback | 函数本身 | useCallback(fn, deps) | 缓存事件处理器 |
| useMemo | 函数返回值 | useMemo(() => fn, deps) | 缓存计算结果 |
| 等价关系 | - | useCallback(fn) === useMemo(() => fn) | - |
记忆方式:
useCallback缓存函数 → 返回 函数本身useMemo缓存值 → 返回 函数执行的结果
// 两种写法等价
const fn1 = useCallback(() => a + b, [a, b]);
const fn2 = useMemo(() => () => a + b, [a, b]);
// fn1 === fn2 // true
注意事项
- useCallback 是性能优化手段,不要过度使用,简单函数直接写就好
- 必须配合 React.memo 或 shouldComponentUpdate 才能真正避免子组件重渲染
- 不要为了用而用,先测量性能瓶颈,再优化
- useCallback 缓存的是函数引用,不是函数的执行结果