前端进阶之旅前端进阶之旅
  • 基础篇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 内联对象 函数 props 失效
ReReact性能与并发

给 memo 子组件传内联对象或函数为什么会失去优化机会?

相同的对象内容不代表相同的引用。让缓存稳定的前提,是业务输入也确实没有改变。

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

先区分数据变化和引用变化

  1. 内联创建相同字面量每次求值仍会得到不同对象或函数
  2. 缓存依赖分类或外部处理函数变了,缓存必须相应失效
  3. 接口简化直接传 category、limit 等基本类型往往更清晰

缓存应服务于已经确认的比较边界;如果接收方本来就要更新,额外维持引用未必有收益。

核心回答

先记住这个答案

memo 默认对每一项 prop 使用 Object.is。父函数每次执行时创建的新对象和箭头函数,即使内容或源码相同,也会被当作变化的输入,子组件因此失去这次跳过的机会。可以优先改传简单值,或者用 useMemo 缓存对象、用 useCallback 缓存函数,并完整声明它们读到的响应式值。稳定引用只能减少无关更新,不能阻止分类、权限或回调真正变化时把新行为传给子组件。

  • 对象内容相等不等于引用相等
  • 依赖必须覆盖对象内容与回调闭包
  • 能简化 props 时先减少缓存维护成本

一个永远变化的 prop 就足够打破比较

设想列表工具栏接收 filters 和 onApply。父组件因无关计数重新执行时,字面量对象与箭头函数都产生新引用;任意一项比较失败,默认 memo 就不能凭其他相等项跳过这轮工作。

不要通过删除函数 prop 或在自定义比较器中忽略回调来伪造稳定。回调可能捕获当前分类或登录身份,忽略它会把视觉上没有变化的按钮变成执行旧业务逻辑的按钮,这属于正确性问题。

按真实输入构造缓存对象和函数

示例让 filters 跟随 category,让 apply 跟随 category 与外部 onPick。增加无关计数时它们保持引用;切换分类时两者更新。外部替换 onPick 后,按钮也必须调用最新处理函数。

这里的 limit 是固定常量,因此不会额外成为组件内的响应式依赖。如果将它改成 prop 或 state,就应加入构造对象的依赖。手工省略依赖换来的稳定,是让新输入失去传播机会。

筛选工具栏的两个稳定输入TSX
import { memo, useCallback, useMemo, useState } from 'react';

const Toolbar = memo(function Toolbar({ filters, onApply }: {
  filters: { category: string; limit: number };
  onApply: () => void;
}) {
  return <button onClick={onApply}>
    应用 {filters.category} / {filters.limit}
  </button>;
});

export default function FilterDemo({ onPick }: {
  onPick: (category: string) => void;
}) {
  const [category, setCategory] = useState('react');
  const [revision, setRevision] = useState(0);
  const filters = useMemo(() => ({ category, limit: 20 }), [category]);
  const apply = useCallback(() => onPick(category), [category, onPick]);
  return <>
    <button onClick={() => setRevision(n => n + 1)}>刷新外层</button>
    <output>外层:{revision}</output>
    <button onClick={() => setCategory(c => c === 'react' ? 'vue' : 'react')}>
      切换分类
    </button>
    <Toolbar filters={filters} onApply={apply} />
  </>;
}

保持分类与处理函数不变时,两个缓存一起支持工具栏的 props 比较。验证必须包含切换分类和替换处理函数,确保优化只跳过无关工作,真实变化仍然进入按钮行为。

也可以消除需要稳定的大对象

如果工具栏只展示分类,接口直接接收 category 即可,limit 可以由合适的所有者提供。组件需要的字段越少,父级无关数据变化越不容易影响它,也少了一层依赖数组维护。

只在 Effect 内使用的配置对象通常可在 Effect 内创建,再依赖基本值;只在计算内部使用的临时对象也可移入计算函数。不要为每一个对象都建立缓存,否则简单代码会变成难以核对的依赖网络。

回答前,多想一步

容易答错的地方

给所有回调都写空数组
空数组不会让闭包自动读取最新分类,它会保留初次缓存的函数及其捕获值。对会变化的输入应声明依赖,或者重构函数接口显式接收参数,不能以隐藏变化来换取比较成功。
把界面更新当成缓存失败
分类变化后工具栏重新执行是期望行为。需要排查的是无关父状态为什么穿透边界,而不是追求所有情况下都保持旧对象;缓存变化与业务数据变化一致才是可维护的优化。
试着用自己的话回答

面试官还会怎么问?

children 也是参与比较的 prop 吗?

是。父级每次创建的元素或数组也可能让 children 引用变化,不能只检查自己命名的业务字段。可以调整组件组合,让局部状态更靠近使用方,并结合实际渲染路径分析,而不是机械缓存全部 JSX。

保持引用能保证组件永远不更新吗?

不能,它仅支持 props 比较路径。子组件自身状态、订阅的 Context 以及重新挂载仍有各自语义;即使没有这些变化,业务正确性也不应依赖 React 必须保留某个优化结果。

同内容的新数组应该深比较吗?

先检查数组为什么被重建、能否保留不可变数据的结构共享,以及子组件计算是否足够昂贵。深比较本身要遍历数据,还容易漏掉回调语义,只有边界受控且实测有收益时才考虑自定义比较。

从一道题,走向一组知识

把知识连起来

性能与并发

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

先建立 memo 默认比较和组件身份的完整判断顺序。

性能与并发

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

了解绕过默认比较时必须承担的行为一致性责任。

性能与并发

useMemo 和 useCallback 有什么区别,应该如何选择?

进一步区分缓存计算结果和缓存函数引用的用途。

参考资料

  • React 官方:memo 的 props 变化
  • React 官方:useMemo
  • React 官方:useCallback

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

本题目录
  1. 先记住这个答案
  2. 一个永远变化的 prop 就足够打破比较
  3. 按真实输入构造缓存对象和函数
  4. 也可以消除需要稳定的大对象
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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