前端进阶之旅前端进阶之旅
  • 基础篇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 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库useMemo 缓存 依赖数组 用法
ReReact性能与并发

useMemo 的缓存和依赖数组是如何工作的?

useMemo 保存的是当前组件位置上的计算结果,不是一张自动积累所有参数组合的全局缓存表。

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

一次筛选计算的三种输入情况

  1. 引用未变items 和 tag 都相同,可复用现有筛选结果
  2. 依赖变化新 items 引用或新 tag 使计算重新执行
  3. 原地突变同一数组内部改动不会被依赖身份比较自动发现

React 可以放弃已有缓存;依赖不变时通常复用结果是性能机制,组件正确性不能依赖这个引用永远存在。

核心回答

先记住这个答案

useMemo 在需要计算时执行传入函数并返回结果,后续渲染逐项用 Object.is 比较依赖;依赖都相同且缓存仍可用时,返回之前的结果。依赖数组应在代码中内联写出、长度固定,并包含计算读取的全部响应式值。对象和数组按引用比较,原地修改内容不会自动触发失效。计算必须保持纯粹,缓存可以被丢弃,因此不能把它当作资源生命周期或业务持久化保证。

  • 依赖按项比较,不会深度观察对象
  • 遗漏依赖会把旧计算结果继续返回
  • 纯计算可重做,副作用不能藏在计算里

依赖必须表达计算真正读取的数据

筛选函数读取 items 与 tag,因此两者都在依赖数组中。只写 tag 会使父级换了一批条目之后仍显示旧结果;只写 items 则会在切换标签后继续展示旧分类,这两种遗漏都不是合法优化。

依赖数组的长度和顺序应保持固定,让每个位置始终对应同一种输入。不要按条件拼接依赖,也不要通过禁用检查规则来隐藏缺项;如果依赖难以说明,通常需要先缩小计算职责。

筛选结果与无关计数分离TSX
import { useMemo, useState } from 'react';

type Item = Readonly<{ id: string; label: string; tag: string }>;

export default function FilteredItems({ items, tag }: {
  items: readonly Item[];
  tag: string;
}) {
  const [revision, setRevision] = useState(0);
  const visible = useMemo(() => items.filter(item => item.tag === tag), [items, tag]);
  return <>
    <button onClick={() => setRevision(n => n + 1)}>增加无关计数</button>
    <output>计数:{revision}</output>
    <ul>{visible.map(item => <li key={item.id}>{item.label}</li>)}</ul>
  </>;
}

增加计数不改变筛选依赖,替换条目数组或标签则改变依赖。readonly 类型帮助表达不可变输入约定,但它不是运行时冻结;调用方仍需要遵守不直接修改共享数据的规则。

相同内容的新数组与同数组突变恰好相反

父级用 map 或展开语法生成内容相同的新数组,引用已经变化,useMemo 会重新计算。缓存并不判断两个数组里的条目是否业务等价;如果重建是无关操作,可先改善数据流或结构共享。

反过来,向同一个数组直接追加条目再触发其他状态更新,引用没有变化,缓存可能继续返回旧筛选结果。应生成新数组交给组件,而不是把数组长度单独加入依赖来修补部分突变,因为修改已有字段仍会漏掉。

它不是多键缓存,也不负责副作用

标签从甲切换到乙,再切回甲时,不要假设最早那份甲结果会从历史表中自动取回。useMemo 的常规契约是围绕当前依赖与最近结果进行复用;若需要多参数缓存,应另行设计容量、失效和生命周期。

开发严格模式可以额外调用计算函数以检查纯度,初次挂载发生挂起等情况也可能丢弃缓存。函数应只计算并返回值,不能在里面创建必须只执行一次的连接、提交订单或累积外部计数。

回答前,多想一步

容易答错的地方

用 JSON 字符串代替所有依赖
序列化会在每次渲染执行并分配字符串,也不能表达函数或任意对象的全部语义。应先维护不可变输入和明确字段,只有特定可序列化数据协议确实需要内容键时,才独立设计该协议。
把缓存结果当作可以随意修改的工作区
修改缓存返回的数组会污染后续复用结果,甚至在新依赖尚未到来时让界面行为取决于外部写入。缓存值应当按派生数据使用,需要用户编辑的数据应进入明确管理的状态。
试着用自己的话回答

面试官还会怎么问?

不传依赖数组会发生什么?

计算会在每次渲染执行,无法依靠依赖相等复用前一次结果。应传入完整且固定的依赖数组;如果计算本来很便宜,也可以直接计算,避免为没有收益的缓存增加阅读成本。

空数组可以保证只计算一次吗?

不能保证。它表示没有需要跟踪的响应式依赖,但严格模式、挂起重试、重新挂载或缓存失效仍可能重新计算。一次性业务行为应有显式的事件、资源生命周期或幂等机制。

useMemo 和 memo 应该同时使用吗?

二者解决不同边界:useMemo 复用计算结果,memo 复用组件渲染。稳定的派生数组传给 memo 子组件时可以配合,但单独昂贵的计算也可能值得缓存,不能把同时使用当作固定套餐。

从一道题,走向一组知识

把知识连起来

性能与并发

为什么 useMemo 只是性能优化,不能用它保证业务正确性?

理解缓存为什么不能承担连接与数据生命周期。

性能与并发

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

观察稳定派生对象如何支持子组件的 props 比较。

Hooks 与复用

eslint-plugin-react-hooks 能发现哪些错误,规则应该怎么配置?

通过静态检查发现遗漏依赖和非法 Hook 调用。

参考资料

  • React 官方:useMemo

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

本题目录
  1. 先记住这个答案
  2. 依赖必须表达计算真正读取的数据
  3. 相同内容的新数组与同数组突变恰好相反
  4. 它不是多键缓存,也不负责副作用
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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