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 需要在每次渲染时对比依赖数组。滥用会导致性能更差,代码可读性降低。
相关推荐
于慨1 天前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz1 天前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶1 天前
前端交互规范(Web 端)
前端
CHU7290351 天前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing1 天前
Page-agent MCP结构
前端·人工智能
王霸天1 天前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航1 天前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界1 天前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript
PieroPc1 天前
一个功能强大的 Web 端标签设计和打印工具,支持服务器端直接打印到局域网打印机。Fastapi + html
前端·html·fastapi
悟空瞎说1 天前
深入 Vue3 响应式:为什么有的要加.value,有的不用?从设计到源码彻底讲透
前端·vue.js