前端进阶之旅前端进阶之旅
  • 基础篇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 useCallback 区别
ReReact性能与并发

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

先说清楚想复用的是“算出来的结果”,还是“交给别人调用的函数”,再选择 Hook。

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

报价按钮中的两条缓存链

  1. 计算结果条目变化时重新计算 total,避免无关更新重复求和
  2. 函数身份total、currency、onPay 不变时复用 submit
  3. 实际调用用户点击时才执行 submit,调用最新应生效的业务输入

示例用简单求和说明依赖关系,不代表小数组求和一定值得缓存;实际收益要结合计算规模和接收方判断。

核心回答

先记住这个答案

useMemo 接收计算函数,需要时执行它并缓存返回值;useCallback 接收函数并返回可复用的函数引用,不会替你执行该业务函数。二者都依赖 Object.is 比较的依赖数组,并要求声明读取的响应式值。需要复用昂贵派生结果或稳定对象时考虑 useMemo,需要让 memo 子组件接收稳定回调时考虑 useCallback。它们都不是请求结果缓存,也不会自动修复闭包中的旧数据。

  • useMemo 缓存计算返回值
  • useCallback 缓存交出去的函数身份
  • 调用时使用什么数据由闭包与参数决定

useCallback 不会帮你调用函数

把箭头函数传给 useCallback 时,JavaScript 仍会求值得到这一轮传入的函数;React 可以返回之前缓存的函数引用。它减少的是下游看到的身份变化,并不是禁止本轮创建函数表达式。

从用途上可将 useCallback 理解为缓存函数本身,而 useMemo 可以通过返回函数达到类似效果。不过计算函数会在渲染期间执行,若误把实际提交操作写成 useMemo 的计算,就可能在用户点击之前触发业务行为。

按派生值和业务回调分别声明依赖

示例先根据 items 求和,再让 submit 捕获 total、currency 和 onPay。无关计数不影响这三项,因此确认按钮可获得稳定回调;切换币种时,即使金额相同,也需要新回调传递新币种。

如果替换条目数组但合计数字相同,求和会重新进行,submit 却仍可能复用,因为它依赖的是合计值而不是整个数组。这种设计成立的前提是提交业务只需要金额和币种,不需要逐条商品信息。

派生金额与确认函数分别缓存TSX
import { memo, useCallback, useMemo, useState } from 'react';

type Line = Readonly<{ price: number; quantity: number }>;
const Confirm = memo(function Confirm({ total, onSubmit }: {
  total: number;
  onSubmit: () => void;
}) {
  return <button onClick={onSubmit}>确认金额 {total}</button>;
});

export default function QuoteDemo({ items, currency, onPay }: {
  items: readonly Line[];
  currency: string;
  onPay: (total: number, currency: string) => void;
}) {
  const [revision, setRevision] = useState(0);
  const total = useMemo(
    () => items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    [items]
  );
  const submit = useCallback(() => onPay(total, currency), [total, currency, onPay]);
  return <>
    <button onClick={() => setRevision(n => n + 1)}>刷新报价外层</button>
    <output>报价计数:{revision}</output>
    <Confirm total={total} onSubmit={submit} />
  </>;
}

金额示例仅演示缓存依赖,实际货币系统应明确最小单位与精度策略。测试要确认没有自动调用 onPay,并在点击、变更币种和替换外部处理函数后验证实际参数。

稳定函数并不等于自动读取最新值

给 submit 写空数组,会保留初次缓存函数捕获的输入,而不会让 total 或 currency 在调用时神奇地变成最新。应补全依赖,或者让调用者通过参数传入需要的数据;重构后仍要核对数据所有权。

缓存一个异步函数也不等于缓存它返回的 Promise。每次调用仍可能发起新请求,竞态、取消、去重和过期策略需要由请求层处理。把函数身份稳定与网络结果复用混在一起,会遗漏真正的数据一致性问题。

回答前,多想一步

容易答错的地方

所有事件回调都套 useCallback
没有接收方依赖函数身份、也没有其他明确缓存收益时,包装会增加依赖维护而不一定减少工作。普通按钮的简单事件处理可以直接定义,确认下游比较或订阅确实需要稳定后再优化。
用 useMemo 发起请求并期待只请求一次
计算函数属于渲染阶段,可能重试且没有相应请求清理协议。数据请求应采用框架数据机制或可管理生命周期的方案,明确缓存键和取消策略,不能用依赖数组代替请求系统。
试着用自己的话回答

面试官还会怎么问?

useMemo 可以返回一个函数吗?

可以,返回值本身没有被限制为对象或数字。但如果目的是缓存函数身份,useCallback 通常更直接;无论写法如何,函数捕获的响应式输入都要被依赖描述,换一种 Hook 不会消除闭包问题。

函数内部用了函数式 setState 就能写空依赖吗?

只针对由更新函数参数提供的那份前态,可以减少直接读取旧 state 的需要。如果函数还读取当前 prop、币种或其他状态,它们仍需列入依赖;函数式更新不是省略全部依赖的通行证。

稳定回调能保证子组件不重新渲染吗?

不能单独保证。子组件还可能收到变化的其他 prop,或者因本地状态、Context 更新而执行;没有相应的复用边界时,稳定一个函数引用也不等于自动阻止父级带来的子组件渲染。

从一道题,走向一组知识

把知识连起来

性能与并发

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

进一步理解计算结果缓存如何比较依赖和失效。

性能与并发

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

观察对象与函数缓存如何配合实际组件边界。

性能与并发

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

明确缓存函数与资源、请求生命周期之间的区别。

参考资料

  • React 官方:useCallback
  • React 官方:useMemo

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

本题目录
  1. 先记住这个答案
  2. useCallback 不会帮你调用函数
  3. 按派生值和业务回调分别声明依赖
  4. 稳定函数并不等于自动读取最新值
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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