面试题:React.memo 和 useMemo 的区别

核心答案

React.memo 管组件,决定组件要不要重新渲染;useMemo 管值,决定一个计算要不要重新执行。

这就是核心,不要再往里面塞细节。


React.memo 到底做了什么?

React.memo 包裹的是组件:

jsx 复制代码
const Child = React.memo(function Child({ name }) {
  console.log('Child render');

  return <div>{name}</div>;
});

当父组件重新渲染时,React 会拿 Child 的新旧 props 做比较。

text 复制代码
父组件重新渲染
      ↓
Child 的 props
      ↓
新旧 props 浅比较
      ↓
没变化 → 可以跳过 Child
变化   → Child 重新渲染

所以 memo 的核心价值是:

父组件更新了,但子组件没必要跟着更新,就把子组件这次渲染跳过去。


memo 为什么用了还是会重新渲染?

最常见的原因就是 props 引用变了。

例如:

jsx 复制代码
<Child user={{ name: 'Tom' }} />

父组件每次渲染都会创建一个新的对象:

js 复制代码
{ name: 'Tom' } !== { name: 'Tom' }

虽然内容一样,但引用不同。数组、函数也是一样:

jsx 复制代码
<Child
  list={[1, 2, 3]}
  onClick={() => console.log('click')}
/>

每次父组件重新执行:

text 复制代码
新的数组引用
新的函数引用
      ↓
props 浅比较失败
      ↓
memo 无法跳过

所以如果确实需要稳定引用,可以:

jsx 复制代码
const user = useMemo(() => ({
  name: 'Tom'
}), []);

const onClick = useCallback(() => {
  console.log('click');
}, []);

然后:

jsx 复制代码
<Child user={user} onClick={onClick} />

这样才是:

text 复制代码
useMemo / useCallback
        ↓
稳定 props 引用
        ↓
React.memo
        ↓
减少子组件重复渲染

memo 能不能阻止所有重新渲染?

不能。它主要解决的是:

父组件更新导致的、但 props 没变化的子组件重复渲染。

例如子组件自己的 state 更新:

jsx 复制代码
const Child = React.memo(() => {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
});

这里:

text 复制代码
setCount()
   ↓
Child 自己发生更新
   ↓
Child 还是会重新渲染

所以不要把 memo 理解成"组件不渲染了"。


useMemo 又做什么?

useMemo 缓存的是一个值。

jsx 复制代码
const result = useMemo(() => {
  return hugeList
    .filter(...)
    .sort(...);
}, [hugeList]);

组件重新渲染时:

text 复制代码
组件重新渲染
      ↓
检查依赖
      ↓
hugeList 没变
      ↓
直接拿之前的 result
      ↓
不用重新计算

所以它解决的是:

组件虽然重新渲染了,但里面有些昂贵计算没必要重复做。

这和 memo 是两个问题。


useMemo 能替代 memo 吗?

不能。

因为:

text 复制代码
React.memo
    ↓
缓存组件的渲染结果
    ↓
减少组件渲染


useMemo
    ↓
缓存一个计算结果
    ↓
减少重复计算

所以:

jsx 复制代码
const Child = React.memo(...);

和:

jsx 复制代码
const result = useMemo(...);

根本不是一个层面的东西。


为什么 useMemo 经常和 memo 一起用?

例如:

jsx 复制代码
const Child = React.memo(function Child({ config }) {
  console.log('Child render');

  return <div>{config.theme}</div>;
});

function Parent() {
  const [count, setCount] = useState(0);

  const config = useMemo(() => ({
    theme: 'dark'
  }), []);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        {count}
      </button>

      <Child config={config} />
    </>
  );
}

这里实际上是两个优化:

text 复制代码
useMemo
  ↓
config 引用保持稳定
  ↓
Child 的 props 没变化
  ↓
React.memo
  ↓
Child 可以跳过渲染

所以可以记成:

useMemo 可以帮助稳定传给 memo 子组件的对象、数组引用,但它本身不负责阻止组件渲染。


useCallback 又是什么?

一句话:

useMemo 缓存值,useCallback 缓存函数。

例如:

jsx 复制代码
const handleClick = useCallback(() => {
  console.log('click');
}, []);

它最常见的用途就是:

text 复制代码
父组件重新渲染
      ↓
不要每次创建新的函数引用
      ↓
Child props 引用保持稳定
      ↓
React.memo 才有机会跳过 Child

所以三者可以这样记:

text 复制代码
React.memo
    ↓
管组件
    ↓
减少不必要的组件渲染


useMemo
    ↓
管值
    ↓
减少不必要的重复计算


useCallback
    ↓
管函数
    ↓
稳定函数引用

useMemo 为什么不能滥用?

因为 useMemo 也不是免费的。每次渲染仍然需要处理:

text 复制代码
依赖比较
缓存管理
内存占用

如果只是:

jsx 复制代码
const total = useMemo(() => a + b, [a, b]);

通常就没必要。因为 a + b 本身非常便宜,缓存它可能比直接计算还复杂。

真正适合的是:

text 复制代码
大数组排序
复杂数据转换
大量过滤
复杂格式化
昂贵计算

或者:

需要稳定对象、数组引用,并且这个引用会影响 memo 子组件是否重新渲染。


高频追问

Q:React.memo 默认比较什么?

新旧 props 的浅比较。

Q:对象 props 为什么容易让 memo 失效?

因为比较的是引用,不是对象内容。

Q:memo 能阻止 state 更新吗?

不能,组件自己的 state 更新仍然会触发更新。

Q:useMemo 能阻止组件重新渲染吗?

不能,它只是缓存计算结果。

Q:useMemo 和 useCallback 的区别?

useMemo 缓存值,useCallback 缓存函数。

Q:是不是所有地方都应该用 useMemo?

不是,计算很便宜或者没有实际性能问题时,没必要为了缓存而缓存。

Q:memo 一定能提升性能吗?

不一定。如果组件本身很轻,或者 props 经常变化,memo 的比较成本可能抵消收益。


最终背诵版

React.memo 管组件,决定组件要不要重新渲染;useMemo 管值,决定一个计算要不要重新执行。memo 主要通过浅比较 props 来跳过不必要的组件更新,useMemo 则是缓存计算结果,避免昂贵计算重复执行。两者可以配合,比如用 useMemo 稳定传给子组件的对象,再用 memo 减少子组件重复渲染。

最短记忆:

text 复制代码
memo     → 组件
useMemo  → 值
useCallback → 函数
相关推荐
派小心.3 分钟前
热力图工具的多页面管理功能怎么比?
前端·数据分析
爱勇宝25 分钟前
裁员裁掉了那个干了14年的人:我这才看清职场的5条潜规则
前端·后端·程序员
Hilaku29 分钟前
GraphQL 在国内为什么水土不服?
前端·javascript·程序员
Latchh37 分钟前
PDF打开不要密码却显示已加密,前端怎么判断
前端·图像处理·人工智能·计算机视觉·pdf
秋天的一阵风38 分钟前
🧐 为什么大厂 RAG 从不用纯向量检索?
前端·面试·ai编程
恋猫de小郭1 小时前
Flutter + EmbeddingGemma 2,谷歌发布完全端侧的 AI Edge Foresight
android·前端·flutter
FITA阿泽要努力1 小时前
第1周·第2讲|一道综合代码推演
服务器·前端·python·agent.
iCxhust1 小时前
如何批量导出edge保存的密码
前端·windows·edge
JarvanMo1 小时前
写 Flutter 不用再套六层 Widget 了
前端
小羊没烦恼!1 小时前
关于大型asp.net应用系统的架构-架构的选择
java·服务器·开发语言·前端·c#