react中useMemo和useCallback的使用场景

先说结论

  1. 为了计算性能 :遇到大数组遍历 、复杂算法 -> 用 useMemo。
  2. 为了引用稳定:
    • 要传给 React.memo 包裹的子组件的对象/数组 -> 用 useMemo。
    • 要传给 React.memo 包裹的子组件的函数 -> 用 useCallback。
    • 函数或对象要放进 useEffect 的依赖数组 里 -> 用 useMemo / useCallback。

除此之外,直接写原生 JS 代码即可。


一、 什么时候使用 useMemo?

useMemo 的作用是缓存计算结果。

1. 进行昂贵的计算(Expensive Calculation)时

如果你的组件内部有一个计算过程非常耗时(例如:遍历数万条数据、复杂的数学运算、图像处理),你不希望每次组件重新渲染(比如只是输入框打字)都重新计算一遍。

  • 标准:如果计算耗时超过 1ms(肉眼不可见,但在低端设备累积会卡顿),或者处理数组长度很大。
  • 代码示例:
ini 复制代码
// ❌ 每次 render 都会运行,导致卡顿
const filteredList = hugeList.filter(item => item.includes(query));

// ✅ 只有当 hugeList 或 query 变化时才重新计算
const filteredList = useMemo(() => {
  return hugeList.filter(item => item.includes(query));
}, [hugeList, query]);

2. 防止子组件不必要的渲染(引用稳定性)

这是最常见但也最容易被忽略的用法。

在 JavaScript 中,{ a: 1 } !== { a: 1 }(每次创建的对象引用地址不同)。

如果你的子组件使用了 React.memo 包裹,但你传给它的 props 是一个在父组件中动态生成的对象 或数组 ,那么 React.memo 会失效,因为每次父组件渲染,生成的对象引用都变了。

  • 代码示例:
javascript 复制代码
const Parent = () => {
  // ❌ 每次 Parent 渲染,config 都是一个新的对象引用
  // 导致 Child 认为 props 变了,从而强制重新渲染
  const config = { color: 'red' }; 
  
  // ✅ 缓存了对象引用,只有依赖变了引用才变
  const memoConfig = useMemo(() => ({ color: 'red' }), []);

  return <Child config={memoConfig} />;
};

// Child 被 memo 包裹
const Child = React.memo(({ config }) => { ... });

二、 什么时候使用 useCallback?

useCallback 的作用是缓存函数引用。

它的核心用途只有一个 :维持函数引用的稳定性,以配合 React.memo 或 useEffect 使用。

1. 将函数传递给经过优化的子组件(React.memo)

这是 useCallback 90% 的使用场景。

如果你把一个函数传给子组件,而子组件用了 React.memo,你不希望父组件渲染时,因为"函数是新创建的"而导致子组件也跟着渲染。

  • 代码示例:
javascript 复制代码
const Parent = () => {
  const [count, setCount] = useState(0);

  // ❌ 每次 Parent 渲染,handleClick 都是一个全新的函数指针
  // 导致 BigList 组件的 props 发生变化,破坏了 React.memo 的效果
  const handleClick = () => {
    console.log('Clicked');
  };

  // ✅ 缓存函数引用,只要依赖不变,handleClick 永远是同一个引用
  const memoHandleClick = useCallback(() => {
    console.log('Clicked');
  }, []);

  return (
    <>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>加一</button>
      
      {/* 如果不传 memoHandleClick,这里的 memo 就白写了 */}
      <BigList onItemClick={memoHandleClick} />
    </>
  );
};

const BigList = React.memo(({ onItemClick }) => {
  console.log('BigList Rendered'); // 使用 useCallback 后,点击"加一"不会触发这里
  return <div>List...</div>;
});

2. 函数作为 useEffect 的依赖项时

如果你的 useEffect 依赖于外部传入的一个函数,为了避免 useEffect 无限循环或者频繁触发,这个函数必须被 useCallback 包裹。

  • 代码示例:
scss 复制代码
function ChatRoom({ roomId, createConnection }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  // 如果 createConnection 没有被 useCallback 包裹,
  // 每次父组件渲染都会导致这里重跑,连接断开又重连
  }, [roomId, createConnection]); 
}

三、 什么时候不需要?(避坑指南)

很多新手会把所有函数和变量都包上,这是错误的。

  1. 简单的计算:
ini 复制代码
// ❌ 没必要,useMemo 本身也有开销(内存分配、依赖比较)
const total = useMemo(() => price * quantity, [price, quantity]);

// ✅ 直接算就好,JS 引擎算这种东西极快
const total = price * quantity;
  1. 子组件没有使用 React.memo:
    如果子组件只是一个普通的 div 或者没有用 memo 包裹的组件,你给它传 useCallback 包裹的函数是完全浪费的。因为不管 props 变没变,子组件都会跟着父组件一起重新渲染。
  2. 仅仅为了"看起来优化了" :
    useMemo 和 useCallback 会占用额外的内存,并且 React 需要在每次渲染时对比依赖数组。滥用会导致性能更差,代码可读性降低。
相关推荐
明月_清风11 分钟前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
Csvn1 小时前
框架性能优化
前端
回眸&啤酒鸭3 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户69371750013843 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员3 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端
小兔子3 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端
IT_陈寒3 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端
guslegend4 小时前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化
海码事务所5 小时前
Google Play 新个人开发者账号上架指南:12 人连续 14 天封闭测试怎么做?
前端
田威AI5 小时前
图片内文字翻译的规格:输入输出、保真、自动化、时间与费用
前端·计算机视觉