WHAT – useCallback 深入理解

内容分享3小时前发布
0 0 0

文章目录

一句话理解场景 1:防止子组件不必要的重新渲染场景 2:在 useEffect 中依赖一个函数场景 3:结合自定义 Hook 传入回调场景 4:配合 useMemo / 复杂计算逻辑场景 5:与性能优化库结合(例如虚拟列表、表格)注意事项总结

在 WHAT – React 函数与 useMemo vs useCallback 我们简单了解过 useCallback。今天我们来深入理解一下。


useCallback
是 React 中一个非常重要但容易被误解的 Hook。今天我们通过 多个典型场景 来理解它的用途和原理。


一句话理解


useCallback(fn, deps)
会返回一个 记忆化(缓存)版本的回调函数
只有当依赖项 (
deps
) 变化时,才会返回新的函数引用。


场景 1:防止子组件不必要的重新渲染

这是最常见的场景。

问题

当父组件重新渲染时,所有在它内部定义的函数都会被重新创建。


import React, { useState } from 'react';

const Child = React.memo(({ onClick }: { onClick: () => void }) => {
  console.log('✅ Child render');
  return <button onClick={onClick}>Click me</button>;
});

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => console.log('clicked');

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <Child onClick={handleClick} />
    </div>
  );
}

👉 每次点击“+1”,父组件重新渲染,
handleClick
被重新创建(函数引用变了)。
即使
Child
用了
React.memo
,它也会重新渲染,因为
onClick
变了。


解决:使用
useCallback


const handleClick = useCallback(() => console.log('clicked'), []);

✅ 现在
handleClick
的引用在依赖
[]
不变时保持稳定。
父组件更新时,
Child
不会重新渲染。


场景 2:在 useEffect 中依赖一个函数

如果你把函数直接写在组件里,而又在
useEffect
中依赖它,会导致无限循环。


useEffect(() => {
  fetchData();
}, [fetchData]); // 🚨 fetchData 每次都是新函数,会导致无限请求

✅ 使用
useCallback
固定函数引用:


const fetchData = useCallback(() => {
  // fetch something
}, []);

useEffect(() => {
  fetchData();
}, [fetchData]); // ✅ 不会无限循环

场景 3:结合自定义 Hook 传入回调

例如在一个自定义 Hook 里需要外部传一个回调。


function useEventListener(event: string, handler: () => void) {
  useEffect(() => {
    window.addEventListener(event, handler);
    return () => window.removeEventListener(event, handler);
  }, [event, handler]);
}

⚠️ 如果
handler
每次都是新函数(没用
useCallback
),
那每次渲染都会移除再添加一次事件监听。

✅ 改用:


const handleKeyDown = useCallback(() => {
  console.log('Pressed key');
}, []);

useEventListener('keydown', handleKeyDown);

场景 4:配合 useMemo / 复杂计算逻辑

比如你在一个 memoized 的计算中依赖一个回调:


const computeValue = useCallback((x: number) => x * 2, []);
const result = useMemo(() => computeValue(10), [computeValue]);

这样可以确保
computeValue
不会频繁变化导致
useMemo
重新计算。


场景 5:与性能优化库结合(例如虚拟列表、表格)

像 Ant Design Table、react-window 等组件会用到
onRow
,
onClick
,
rowRenderer
等回调。
如果不使用
useCallback
,每次渲染都会创建新的函数,造成性能浪费或无法缓存。


const onRowClick = useCallback((record) => {
  console.log('Clicked row:', record);
}, []);

注意事项

不是所有函数都要用
useCallback

它会增加内存开销和心智负担。

一般只在以下场景使用:

函数传给
React.memo
的子组件;函数作为
useEffect
依赖;函数传给性能敏感组件(虚拟列表、大表格等)。

依赖数组很重要!

若函数中引用了外部状态变量,必须把它们加进依赖数组中。


const handleClick = useCallback(() => {
  console.log(count);
}, [count]); // ✅ count 是依赖


useCallback(fn, deps)

useMemo(() => fn, deps)

本质上
useCallback

useMemo
的一个语法糖。


总结

场景 是否适合用 useCallback 说明
子组件 props 是函数且使用 React.memo 避免子组件重复渲染
函数放在 useEffect 依赖中 避免无限循环
自定义 Hook 接受回调 避免重复副作用
函数只在本组件使用 没必要缓存
简单 onClick/onChange 直接写即可
© 版权声明

相关文章

暂无评论

none
暂无评论...