← 返回题目列表

React.memo 的作用是什么?为什么有时不生效?

高频 中等 第 11 / 27 题 更新于 2026/07/27
ReactReact.memo渲染优化

简化版

React.memo 用于缓存函数组件渲染结果,当 props 浅比较没有变化时跳过重新渲染。它不阻止组件自身 state、context 变化导致的渲染;如果 props 里每次传新对象或新函数,也会导致 memo 失效。

详细版

示例:

const Child = React.memo(function Child({ user }) {
  return <div>{user.name}</div>;
});

默认比较是浅比较:

<Child user={{ name: 'Tom' }} />

这里每次父组件 render 都创建新对象,user 引用变化,Child 仍会重新渲染。

可配合 useMemouseCallback 稳定 props,也可以传自定义比较函数,但要谨慎。

完整版教学

一、React.memo 优化的是父组件带来的重渲染

父组件重新渲染时,默认会重新计算子组件。React.memo 给子组件加一道门:如果新旧 props 浅比较相同,就复用上次结果。

它适合纯展示组件、props 稳定、渲染成本较高的组件。

二、浅比较的含义

浅比较只比较第一层引用。基本类型按值比较,对象、数组、函数按引用比较。

const options = { size: 'large' };

如果这个对象写在父组件函数体里,每次 render 都是新引用。即使内容一样,memo 也认为变了。

三、memo 不能阻止所有更新

如果 memo 组件内部调用了 useState,自己的状态变化会重新渲染。如果它使用了 context,context value 变化也会重新渲染。

所以 memo 不是“冻结组件”,只是根据 props 判断是否跳过父级传导下来的更新。

四、面试追问与工程落地

常见追问是“自定义比较函数是不是越深越好”。不是。深比较可能比重新渲染更贵,而且容易漏掉函数闭包变化。只有 props 结构稳定且比较成本明确低于渲染成本时,才考虑自定义比较。

工程中更常见的优化是拆组件、缩小 props、稳定引用、避免把整个大对象传给子组件。比如传 user.name 比传完整 user 更容易 memo 命中。

五、默认比较、命中条件与成本模型

memo 默认逐个比较新旧 props,并对每个 prop 使用 Object.is。只有所有 props 都相等时才有机会跳过父级导致的重新渲染;它是性能优化而不是语义保证,React 仍可能因自身 state、订阅的 Context 或内部需要而执行组件。

收益 ≈ 跳过的渲染成本
代价 ≈ props 比较成本 + 缓存/代码复杂度
只有收益长期大于代价,memo 才值得保留

假设列表有 100 行,父组件每秒因计时器更新 10 次,每行渲染约 0.05 ms:全部行重复执行的理论成本约为 100 × 10 × 0.05 = 50 ms/s。若行 props 稳定,memo 可能省下大部分成本;若每行都收到新建对象,比较仍失败,反而多付一层检查。

// 更容易命中:只传子组件真正需要的原始值
<UserRow name={user.name} selected={user.id === selectedId} />
// 更难命中:每轮组装一个新对象
<UserRow viewModel={{ ...user, selected: user.id === selectedId }} />

缩小 props 还能降低组件耦合,不只是服务于 memo。若上游数据采用不可变更新,未变化实体可保留引用,引用比较才能可靠表达“内容没有变”。

更新来源memo 能否直接拦截原因
父组件重渲染且 props 全相等通常可以命中 props 比较
自身 setState不可以组件自己的状态变了
读取的 Context 变化不可以Context 订阅独立于 props
对象/函数 prop 新引用默认不可以Object.is 比较失败

六、自定义比较与 React Compiler 的边界

自定义 arePropsEqual 必须比较每一个 prop,包括函数。若错误地把新旧 onClick 判成相等,子组件可能保留旧闭包,点击时读取旧 props/state;无界深比较还可能比组件渲染更慢,甚至在数据结构扩展后出现严重卡顿。

启用 React Compiler 的项目通常不再需要大量手写 memo,编译器会自动应用等价的组件和值缓存。但这不是所有 React 项目默认都已启用的运行时能力;未采用 Compiler、第三方边界或经测量的特殊热点仍可能使用手工 memoization。

优化顺序:先让 props 更小且稳定,再测组件渲染成本,最后决定 memo;不要用深比较掩盖失控的数据结构。

七、常见误区与追问

  • 误区:React.memo 保证组件绝不重新渲染。 它只是基于 props 的性能优化,state、Context 和 React 内部决策仍可能触发执行。
  • 误区:浅比较就是递归比较一层对象字段。 默认是逐个 prop 用 Object.is,对象 prop 仍按引用比较。
  • 误区:自定义深比较一定比重新渲染快。 比较成本取决于数据规模与深度,必须用生产模式测量。
  • 追问:为什么 useCallback 后子组件仍渲染? 子组件可能没用 memo,其他 props 变化,或它自己的 state/Context 已变化。
  • 追问:自定义比较为什么必须比较函数? 函数闭包携带创建时的 props/state,忽略变化会让交互读取旧数据。
  • 追问:React Compiler 是否让 memo 完全过时? 启用 Compiler 时通常可减少手写 memo;未启用项目仍按实际瓶颈选择。
  • 追问:什么组件不值得 memo? 渲染很轻、props 几乎每次都变,或比较成本接近渲染成本的组件通常收益很低。

八、加强记忆

React.memo 是“props 浅比较门卫”。props 引用稳定才守得住;state、context、自身更新不归它管。

先缩小 props、再测渲染成本、最后决定缓存,启用 React Compiler 时还要重新评估手写 memo 的必要性。