详解 React useCallback & useMemo

前言:useCallback 和 useMemo,你真的用对了吗?

今天来聊聊 React 中两个容易混淆的 Hook:useCallback 和 useMemo。很多人用了很久 React,却还是搞不清楚它们的具体区别和使用场景。别担心,看完这篇文章,你一定会豁然开朗!

useCallback:不只是缓存函数

useCallback 的作用

简单来说,useCallback 就是用来缓存函数的。它返回一个记忆化的回调函数,只有当依赖项发生变化时,这个函数才会重新创建。

scss 复制代码
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b], // 依赖项
);

useCallback 的应用场景

1. 性能优化:避免子组件不必要的重渲染

这是 useCallback 最常用的场景。当父组件传递函数给子组件时,如果父组件重渲染,每次都会创建新的函数实例,导致子组件即使用了 React.memo 也会重新渲染。

javascript 复制代码
// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // 使用 useCallback 缓存函数
  const handleClick = useCallback(() => {
    console.log('按钮被点击了');
  }, []); // 空依赖数组表示永远不会重新创建
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>增加:{count}</button>
      <Child onClick={handleClick} />
    </div>
  );
};

// 子组件用 React.memo 包裹
const Child = React.memo(({ onClick }) => {
  console.log('子组件渲染了');
  return <button onClick={onClick}>子按钮</button>;
});

2. 作为其他 Hook 的依赖项

当函数被作为 useEffect、useMemo 等 Hook 的依赖项时,使用 useCallback 可以避免 effect 不必要的重复执行。

scss 复制代码
const fetchData = useCallback(async () => {
  const response = await fetch('/api/data');
  return response.json();
}, []); // 注意:这里如果真的有依赖项,一定要如实填写

useEffect(() => {
  fetchData();
}, [fetchData]); // 因为 fetchData 被 useCallback 缓存,effect 不会频繁执行

useCallback 的常见误区

不要滥用 useCallback! 很多开发者习惯给每个函数都包上 useCallback,这其实是不对的。

useCallback 本身也有性能开销(比较依赖项),只有在真正需要时使用它:

  • 函数作为子组件的 props
  • 函数作为其他 Hook 的依赖项
  • 函数内部有复杂计算(但这种情况其实更适合 useMemo)
javascript 复制代码
// 没必要用 useCallback!
const simpleFunction = () => {
  console.log('很简单的方法');
};

// 需要用 useCallback
const complexFunction = useCallback(() => {
  // 复杂计算或操作
}, [dependency]);

useMemo:缓存计算结果

useMemo 的作用

useMemo 用来缓存计算结果,只有当依赖项发生变化时,才会重新计算。

scss 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo 的应用场景

1. 优化昂贵计算

当有计算量很大的函数时,使用 useMemo 可以避免每次渲染都重新计算。

javascript 复制代码
const ExpensiveComponent = ({ data }) => {
  // 只有 data 变化时才会重新计算
  const processedData = useMemo(() => {
    console.log('正在进行昂贵计算...');
    return data.map(item => ({
      ...item,
      fullName: `${item.firstName} ${item.lastName}`,
      score: calculateScore(item) // 假设这是个复杂计算
    }));
  }, [data]);

  return <div>{/* 使用 processedData */}</div>;
};

2. 避免对象和数组的重复创建

在 JavaScript 中,每次渲染都会创建新的对象和数组,即使内容完全一样。这可能导致子组件不必要的重渲染。

javascript 复制代码
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // 不使用 useMemo:每次渲染都会创建新对象
  // const config = { type: 'button', style: 'primary' };
  
  // 使用 useMemo:只有依赖变化时才创建新对象
  const config = useMemo(() => ({
    type: 'button',
    style: 'primary'
  }), []); // 空依赖:永远不会重新创建

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>点击次数:{count}</button>
      <Child config={config} />
    </div>
  );
};

const Child = React.memo(({ config }) => {
  console.log('子组件渲染了');
  return <button className={config.style}>子按钮</button>;
});

useCallback vs useMemo:到底有什么区别?

简单来说:

  • useCallback 缓存函数本身
  • useMemo 缓存函数的返回值

看个例子就明白了:

scss 复制代码
// 这两个表达式是等价的:
useCallback(fn, deps);
useMemo(() => fn, deps);

useCallback 返回的是函数,useMemo 返回的是函数执行的结果。

结语:合理使用,不要滥用

useCallback 和 useMemo 都是性能优化工具,但并不是用的越多越好。不当使用反而会让代码更复杂,甚至降低性能。

使用原则:

  1. 先写没有优化的代码,发现有性能问题后再优化
  2. 使用 React DevTools 的 Profiler 分析性能瓶颈
  3. 不要为了优化而优化,保持代码可读性更重要

记住:最好的优化是不需要优化。写出清晰、简洁的代码比过度使用优化 Hook 更重要!

相关推荐
Mintopia1 小时前
⚡ WebAssembly 如何加速 AIGC 模型在浏览器中的运行效率?
前端·javascript·aigc
AAA_Tj1 小时前
前端动画技术全景指南:四大动画技术介绍
前端
断竿散人1 小时前
乾坤微前端框架的沙箱技术实现原理深度解析
前端·javascript·前端框架
用户784721509911 小时前
Zustand源码解读(更新中)
react.js
进阶的鱼1 小时前
(4种场景)单行、多行文本超出省略号隐藏
前端·css·面试
月亮慢慢圆1 小时前
拖拽API
前端
知了一笑1 小时前
独立做产品,做一个,还是做多个找爆款?
前端·后端·产品
uhakadotcom1 小时前
在python中,使用conda,使用poetry,使用uv,使用pip,四种从效果和好处的角度看,有哪些区别?
前端·javascript·面试
_AaronWong2 小时前
Electron 桌面应用侧边悬浮窗口设计与实现
前端·electron