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 需要在每次渲染时对比依赖数组。滥用会导致性能更差,代码可读性降低。
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax