前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库React.memo PureComponent shouldComponentUpdate 区别
ReReact性能与并发

React.memo、PureComponent 和 shouldComponentUpdate 有什么区别?

两者都能减少无关渲染,但比较范围不同;迁移类组件时,不能只把比较函数换一个名字。

前端进阶之旅 · 一题精讲更新于 2026.09.06
React#性能与并发#React 性能#性能优化#并发编程
先看核心答案读代码示例
理解线索

把比较范围与更新语义分开

  1. PureComponent类组件继承浅比较,比较 props 与 state 各项值
  2. memo函数组件常用的 props 优化包装,自身状态仍可更新
  3. 自定义判断shouldComponentUpdate 为真要更新,arePropsEqual 为真可跳过

不要在 PureComponent 上再覆盖 shouldComponentUpdate;需要完整自定义类组件策略时,应使用 Component 并清楚定义判断。

核心回答

先记住这个答案

PureComponent 是类组件的基类,提供对 props 和 state 的浅比较;memo 返回一个具有 props 优化边界的组件,函数组件自己的 state 仍按 Hook 更新语义处理。普通类组件可以通过 shouldComponentUpdate 自定义更新判断,其 true 表示需要渲染,而 memo 的 arePropsEqual 返回 true 表示输入等价、可以跳过。它们都要求输入不可变,也不能阻止所消费的 Context 发生有效更新。

  • PureComponent 浅比较 props 与 state
  • memo 的比较器只负责 props 边界
  • 两个自定义判断函数的布尔方向相反

名字相近,但不是同一份比较规则

PureComponent 会浅比较状态对象中的各项值。函数组件调用 useState setter 时,则由该 Hook 的更新机制处理值是否相同;memo 并不接收函数组件的整个内部状态,也不会替它实现类状态对象的浅比较。

因此将类中的对象 state 换成一个 useState 后,每次创建内容相同的新对象并不等于维持旧状态值。需要先设计状态边界,再讨论 memo,不能假设类组件的所有优化条件会随语法迁移自动保留。

同一输入下,两种组件仍能独立展开

下面两个组件接收同一个 score,却各自拥有 expanded。点击函数版展开不会改变类版,点击类版展开也不会被 PureComponent 拦住,因为状态项确实发生变化。父级无关计数则提供重复 props 的观察入口。

这个例子用简单文本是为了观察行为边界,没有宣称它值得性能优化。真实迁移还要核对生命周期、事件闭包和状态结构,先确认两种组件的用户行为一致,再测量不必要的渲染是否减少。

类组件与函数组件的独立状态TSX
import { memo, PureComponent, useState } from 'react';

export class ClassScore extends PureComponent<{ score: number }, { expanded: boolean }> {
  state = { expanded: false };
  render() {
    return <section>
      <button onClick={() => this.setState(s => ({ expanded: !s.expanded }))}>
        类版展开
      </button>
      <p>类版:{this.props.score} / {String(this.state.expanded)}</p>
    </section>;
  }
}

export const FunctionScore = memo(function FunctionScore({ score }: { score: number }) {
  const [expanded, setExpanded] = useState(false);
  return <section>
    <button onClick={() => setExpanded(value => !value)}>函数版展开</button>
    <p>函数版:{score} / {String(expanded)}</p>
  </section>;
});

export default function ComparisonDemo() {
  const [revision, setRevision] = useState(0);
  const [score, setScore] = useState(80);
  return <>
    <button onClick={() => setRevision(n => n + 1)}>无关更新</button>
    <output>父计数:{revision}</output>
    <button onClick={() => setScore(n => n + 1)}>修改分数</button>
    <ClassScore score={score} />
    <FunctionScore score={score} />
  </>;
}

同分数的父级更新和组件自身展开是两类操作。比较应包含父级计数、修改分数以及两侧独立展开,避免只看某次跳过日志就误以为内部状态也被缓存冻结。

迁移比较函数时先写出真值含义

shouldComponentUpdate 的参数是下一份 props、state,旧值可以从类实例读取;arePropsEqual 的两个参数是旧 props 与新 props,且返回方向相反。直接复制原来的布尔表达式可能把更新和跳过完全颠倒。

类 setState 对对象进行浅合并,useState setter 则替换这一份状态值。如果原代码只提交一个字段,迁移后通常需要显式保留其他字段,或拆成独立状态;这属于数据语义变化,与 memo 是否存在无关。

回答前,多想一步

容易答错的地方

认为 memo 会浅比较内部对象 state
memo 的自定义比较器看不到 Hook 内部状态。向 useState 提交新对象时,需要依照该 Hook 的相等性语义分析;保留旧对象并修改字段也不是优化方案,会让状态快照和依赖关系失真。
把类更新判断原样传给 memo
两个接口的参数结构和布尔含义都有差异。应列出相同输入、字段变化和函数变化三个用例,明确哪些需要更新,然后重写比较表达式;简单复制甚至盲目取反都可能漏掉行为依赖。
试着用自己的话回答

面试官还会怎么问?

为什么不可变更新对两者都重要?

浅比较依赖新旧值仍然保有各自的身份和内容。直接修改共享对象会使旧输入也被污染,比较无法还原变化前状态;创建发生变化路径的新对象,才能让比较可靠地识别有效更新。

Context 变化会穿过这两种优化吗?

组件实际消费的 Context 值变化仍会触发相应更新。若大范围订阅导致高频重算,应拆分提供者或缩小消费范围;props 浅比较不能替代对订阅来源和状态位置的设计。

新代码应该为了优化改写成类组件吗?

不需要。函数组件可以通过合理状态边界和必要的 memo 获得优化,启用 React Compiler 的项目还可能获得自动缓存。选择类组件应有现存代码或框架约束,而不是把 PureComponent 当作更强的通用性能开关。

从一道题,走向一组知识

把知识连起来

性能与并发

React.memo 的 arePropsEqual 如何编写,为什么不能忽略函数 prop?

检查自定义等价比较如何同时保留显示与事件行为。

状态与渲染

React state 设为相同值会跳过什么,Object.is 有哪些容易误判的情况?

继续区分 useState 值相等与组件 props 浅比较。

性能与并发

React.memo 在什么条件下能跳过子组件渲染?

回到父级更新时 memo 能跳过工作的基本条件。

参考资料

  • React 官方:PureComponent
  • React 官方:memo

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 名字相近,但不是同一份比较规则
  3. 同一输入下,两种组件仍能独立展开
  4. 迁移比较函数时先写出真值含义
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑