React.memo 与 PureComponent 应用:浅比较原理、使用场景、与 shouldComponentUpdate 对比
本章聚焦 React 性能优化三大核心手段——React.memo、PureComponent、shouldComponentUpdate。掌握浅比较原理、适用边界、触发条件,是面试中区分“初级优化”与“资深优化”的关键分水岭。
💡 速记要点
• 浅比较:
• 使用场景:子组件渲染成本 > 浅比较成本 + props 多数不变
• 退化场景:内联对象/函数每次 render 创建新引用 → 优化失效
• 浅比较:
=== 比较引用,仅第一层属性• 使用场景:子组件渲染成本 > 浅比较成本 + 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.”
• 主动说出“浅比较成本约 0.0x ms,而虚拟 DOM diff 成本约 x ms,只有节省的 diff 成本 > 浅比较成本才有意义”
• 提到“React.memo 在 React 18 自动批处理环境下效果更明显”
• 引用 React 官方文档:“Use React.memo wisely: it’s not a silver bullet.”