React.memo 与 PureComponent 应用:浅比较原理、使用场景、与 shouldComponentUpdate 对比

本章聚焦 React 性能优化三大核心手段——React.memo、PureComponent、shouldComponentUpdate。掌握浅比较原理、适用边界、触发条件,是面试中区分“初级优化”与“资深优化”的关键分水岭。

React.memo / PureComponent 回答框架 浅比较原理 使用场景 纯展示|props未变 vs shouldComponentUpdate 类组件 vs 函数组件 性能考量 比对成本|子组件树 最佳实践 key|不可变数据|useCallback 核心:浅比较锁定避免渲染 三者对比:React.memo vs PureComponent vs shouldComponentUpdate 特性 React.memo (HOC) PureComponent (类) shouldComponentUpdate 组件类型 函数组件 类组件(继承) 类组件(方法) 比较方式 浅比较 props 浅比较 props & state 自定义逻辑 是否可自定义比较 可传第二个参数 不可自定义 完全自定义 推荐场景 新项目、函数组件 遗留类组件重构 复杂比对需求
💡 速记要点
• 浅比较:=== 比较引用,仅第一层属性
• 使用场景:子组件渲染成本 > 浅比较成本 + props 多数不变
• 退化场景:内联对象/函数每次 render 创建新引用 → 优化失效

📌 面试问答卡片

🔹 Q1:React.memo 的浅比较原理是什么?什么情况下失效?

考察点是否理解引用比较 vs 值比较;能否识别常见失效场景
回答框架定义浅比较 → 逐层说明比较规则 → 列举失效场景(内联对象/函数/数组) → 给出解决方案(useMemo / useCallback)
示例回答React.memo 默认对 prevProps 与 nextProps 做浅比较,即 Object.is 比较每个 key 的值。对于对象、数组,只比较引用地址。失效场景如每次 render 内联创建新对象:<Child data={{a:1}} />,引用总变化,memo 无效。需用 useMemo 稳定引用。
可能追问浅比较遇到嵌套对象怎么办?手写深比较代替 memo 合适吗?
避坑提醒⚠️ 不要在 memo 的第二个参数中做深比较(如 JSON.stringify),会引入新性能问题;优先通过不可变数据范式保证浅比较有效。

🔹 Q2:PureComponent 和 shouldComponentUpdate 有什么区别?各自适用于什么场景?

考察点类组件生命周期掌握程度;能否根据不同场景选择正确的优化方案
回答框架PureComponent:自动浅比较 props+state → shouldComponentUpdate:手动控制 → 对比灵活性/性能开销 → 实际选择依据
示例回答PureComponent 继承后自动浅比较,无需手动写逻辑;shouldComponentUpdate 需返回 boolean。PureComponent 适合“大部分 props 为基本类型”的简单组件;SCU 适合需要精细控制(如只比较某几个字段)或存在深嵌套数据的复杂组件。
可能追问PureComponent 内部做了哪些优化?为什么 React 官方更推荐 React.memo 而不是 PureComponent?
避坑提醒⚠️ PureComponent 内部使用 shouldComponentUpdate 实现,但要注意 state 浅层变更后子组件未更新?通常因直接修改 state 引用未变;必须使用不可变更新。

🔹 Q3:React.memo 与 useMemo 有什么关系?能否用 useMemo 代替 React.memo 来避免子组件重渲染?

考察点对 Hooks 系列优化工具的边界理解;避免概念混淆
回答框架区分 memoisze 的是组件(React.memo) 还是值/计算(useMemo) → useMemo 不能直接包裹组件返回 JSX → 正确组合:React.memo + useMemo/useCallback 稳定 props
示例回答React.memo 缓存组件自身,决定跳过渲染;useMemo 缓存计算结果。不能直接 useMemo(() => <Child />, [deps]) 代替 React.memo,因为父组件重新渲染时仍然会创建新组件实例。正确用法:子组件用 React.memo 包裹,父组件用 useMemo 稳定 props 对象引用。
可能追问useMemo 包裹的组件内使用 state 会怎样?React.memo 和 React.lazy 可以一起用吗?
避坑提醒⚠️ 初学者常误用 useMemo 包裹 <Child /> 来“优化”子组件,这并不能阻止子组件因 props 变化而重渲染;必须配合 React.memo 或内部 shouldComponentUpdate。

🚀 项目案例:列表性能优化实战

项目名称超长可编辑表格组件优化
业务背景内部 CRM 系统的数据录入页,表格最大支持 2000 行 × 20 列,每行含表单输入、下拉、日期等组件。
规模指标单页 2000 行渲染;初始化耗时 8s → 优化至 1.2s;滚动卡顿 → FPS 从 20 提升至 55+。
技术栈React 18 + TypeScript + Ant Design + @tanstack/react-virtual + Immutable.js
核心难点① 每行数据变化只影响该行,但父组件 re-render 导致全部行重渲染;② 输入框 onChange 触发全表渲染;③ 下拉选项数据需要从 props 传递,但每次 parent state 更新会重建整个 options 数组。
面试高频追问• 为什么用 Immutable.js?如何与浅比较协同?
• 虚拟列表后 React.memo 是否还有必要?
• 如何度量优化收益(Profiler / 计时)?
可迁移回答话术“核心是将每行提取为独立 Row 组件并用 React.memo 包裹,同时确保行数据通过 useMemo 保持引用稳定。对于 options 等配置,在父组件中用 useMemo 缓存,避免每次 state 变化重建对象。再配合虚拟滚动减少 DOM 节点,最终达到百米秒级响应。”
📐 面试加分行为
• 主动说出“浅比较成本约 0.0x ms,而虚拟 DOM diff 成本约 x ms,只有节省的 diff 成本 > 浅比较成本才有意义”
• 提到“React.memo 在 React 18 自动批处理环境下效果更明显”
• 引用 React 官方文档:“Use React.memo wisely: it’s not a silver bullet.”