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 需要在每次渲染时对比依赖数组。滥用会导致性能更差,代码可读性降低。
相关推荐
JS_GGbond2 小时前
前端水印实战:给你的页面穿上“隐形盔甲”
前端
Sthenia2 小时前
如何用 Chrome DevTools 定位 Long Task:一份从零到实战的排查笔记
前端·性能优化
用户22264598943412 小时前
CSS单位全解析:从像素到视口的响应式设计
前端
Mapmost2 小时前
【实景三维】还再为渲染发愁?手把手教你大场景如何实现“精细”与“流畅”平衡!
前端
钱多多8102 小时前
Vue版本降级操作指南(解决依赖冲突与版本不一致问题)
前端·javascript·vue.js·前端框架
San302 小时前
深度解析 React 组件化开发:从 Props 通信到样式管理的进阶指南
前端·javascript·react.js
AAA阿giao2 小时前
深度解析 React 项目架构:从文件结构到核心 API 的全面拆解
前端·javascript·react.js
LYFlied2 小时前
Vue3虚拟DOM更新机制源码深度解析
前端·算法·面试·vue·源码解读
1024肥宅2 小时前
综合项目实践:小型框架/库全链路实现
前端·面试·mvvm