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. 仅仅为了"看起来优化了"
    useMemouseCallback 会占用额外的内存,并且 React 需要在每次渲染时对比依赖数组。滥用会导致性能更差,代码可读性降低。
相关推荐
QQ1__8115175158 小时前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态8 小时前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子8 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室8 小时前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI8 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing8 小时前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者8 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册8 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李8 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢8 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web