尧图精选

VueUse useMemoize 完全指南:基于参数的响应式函数结果缓存

🕒 发布时间:2026/10/1 22:49:48 📁 来源:尧图网络
前端【免费下载链接】vueuseCollection of essential Vue Composition Utilities for Vue 3项目地址https://gitcode.com/gh_mirrors/vu/vueuse点击查看免费下载导读useMemoize是 VueUse 提供的一个核心工具函数用于按参数缓存函数的执行结果并保持响应式。它既支持同步函数也支持异步函数——对于异步场景它会复用已发出的 Promise避免同一时间重复请求相同数据。本文将以 官方文档 为主体结合 源码实现 与 测试用例系统讲解它的缓存原理、五种核心 API、自定义缓存键与自定义缓存机制以及它与computed/computedAsync组合实现响应式数据缓存的最佳实践。核心概念为什么要用 useMemoize在前端应用中我们经常遇到这类需求根据某个 ID 获取用户信息、根据关键字查询列表、根据配置对象计算结果。如果没有缓存每次调用都会触发一次网络请求或一次昂贵的计算如果缓存策略设计不当又会遇到“数据更新后界面不刷新”的响应式丢失问题。useMemoize的核心价值在于三点按参数缓存相同参数的调用直接命中缓存不再执行底层函数天然响应式缓存容器本身由 Vue 的shallowReactive包裹配合computed使用时缓存内容变化会自动触发依赖更新异步安全异步结果以 Promise 形式缓存并发的相同请求会复用同一个 Promise从根源上避免重复请求。从源码结构看useMemoize的实现非常轻量——index.ts 仅约 127 行且标注了__NO_SIDE_EFFECTS__意味着它不会产生副作用可以被安全地 tree-shaking 和按需引入。在 export-size.md 的统计中useMemoize的体积为 242 B是一个成本极低的工具函数。快速上手基础用法useMemoize接收两个参数一个resolver函数即真正执行计算的函数和可选的options配置对象返回一个记忆化后的函数。import { useMemoize } from vueuse/core const getUser useMemoize( async (userId: number): PromiseUserData axios.get(users/${userId}).then(({ data }) data), ) const user1 await getUser(1) // 首次调用真正请求 users/1 const user2 await getUser(2) // 首次调用真正请求 users/2 // ... 中间过程 const user1 await getUser(1) // 命中缓存直接返回不再发请求 // 强制刷新绕过缓存直接重新请求 const user1 await getUser.load(1) // 按参数删除缓存 getUser.delete(1) // 清空全部缓存 getUser.clear()注意这里的调用方式getUser(1)是先查缓存未命中再执行而getUser.load(1)是无条件执行并刷新缓存。二者的语义区别正是日常开发中读缓存与主动刷新的典型场景。如果底层函数不需要参数useMemoize同样适用——测试用例 index.browser.test.ts 验证了无参数时只执行一次 resolver后续调用全部命中缓存const memo useMemoize(() result) memo() // 执行一次 memo() // 命中缓存不再执行多个参数的场景同样按完整参数组合缓存memo(1, 1)与memo(1, 2)会生成两个不同的缓存条目见 测试用例。五个核心 API根据源码中的UseMemoizeReturn接口index.tsuseMemoize返回的对象具备以下能力API类型作用(...args)直接调用(...args) Result从缓存取值未命中则执行 resolver 并写入缓存load(...args)(...args) Result无条件执行 resolver 并刷新缓存delete(...args)(...args) void删除指定参数对应的缓存条目clear()() void清空全部缓存generateKey(...args)(...args) CacheKey生成给定参数对应的缓存键cacheUseMemoizeCache暴露底层缓存容器便于检查或注入自定义缓存其中generateKey和cache是额外暴露的两个属性前者让你在外部也能拿到当前参数对应的缓存键例如配合cache.has(key)做预判断后者则把缓存容器本身暴露出来方便调试或与自定义缓存策略协同。与 computed / computedAsync 组合让缓存活起来纯缓存函数只是减少重复计算而useMemoize的真正威力在于与 Vue 响应式系统的结合。官方文档给出了与computedAsync组合的示例import { computedAsync, useMemoize } from vueuse/core const getUser useMemoize( async (userId: number): PromiseUserData axios.get(users/${userId}).then(({ data }) data), ) const user1 computedAsync(() getUser(1)) // ... await getUser.load(1) // 强制刷新后user1 会自动更新这里的原理链路值得拆解useMemoize的缓存容器是shallowReactive(new Map(...))见 index.ts它是一个响应式对象computedAsync内部通过watchEffect追踪依赖见 computedAsync 源码当回调中访问getUser(1)时会建立对缓存 Map 的依赖追踪调用getUser.load(1)时cache.set(key, value)触发了 Map 的响应式更新computedAsync收到通知后重新求值进而让user1的值刷新。测试用例 index.browser.test.ts 直接验证了这条响应式链路先用memo(1)得到旧值再memo.load(1)刷新缓存computed(() memo(1))的值会从result-1更新为new-result-1。如果你不想引入异步计算直接用computed包裹也是完全可行的——computed(() getUser(1))同样会随load(1)的刷新而更新。这种缓存 强制刷新 响应式的组合非常适合做下拉列表数据、详情页数据等需要手动刷新的场景。自定义缓存键 getKey解决复杂参数的性能与语义问题useMemoize默认使用JSON.stringify(args)将参数数组序列化为缓存键见 index.ts。这意味着相等的对象结构相同会得到相同的缓存键从而命中同一缓存条目参数顺序影响缓存键memo(1, 2)与memo(2, 1)是两个不同的条目。但JSON.stringify存在明显的性能隐患。官方文档给出了明确警告对于大型或复杂的对象使用JSON.stringify作为默认键生成器可能非常慢。为了在复杂参数下获得更好的性能强烈建议提供自定义的getKey函数基于原始值或唯一标识生成键。实践中常见的做法是只取参数中的关键字段如id参与键生成忽略 headers、配置对象等不参与缓存语义的参数import { useMemoize } from vueuse/core const getUser useMemoize( async (userId: number, headers: AxiosRequestHeaders): PromiseUserData axios.get(users/${userId}, { headers }).then(({ data }) data), { // 只使用 userId 作为缓存键忽略 headers getKey: (userId, headers) userId, }, )这样设置后即使headers中携带了不同的 token 或租户信息只要userId相同就会共享缓存。这在语义上可能正是你想要的减少重复请求但也提醒我们设计 getKey 本质上是定义什么条件下结果可以复用。getKey的签名是(...args) string | number见 index.ts即键必须是原始值类型。测试用例 index.browser.test.ts 演示了一个取模键的极端例子getKey: (arg1) arg1 % 2会让memo(1)与memo(3)命中同一个缓存条目memo(2)与memo(4)命中另一个——这既验证了自定义键的灵活性也直观展示了键决定缓存粒度这一核心思想。自定义缓存机制替换默认的 Map默认情况下缓存结果存储在一个Map中。如果你需要缓存淘汰LRU、TTL 过期、持久化存储如 localStorage或分布式缓存可以通过cache选项注入自己的实现。官方文档给出了缓存接口的完整结构export interface MemoizeCacheKey, Value { /** * Get value for key */ get: (key: Key) Value | undefined /** * Set value for key */ set: (key: Key, value: Value) void /** * Return flag if key exists */ has: (key: Key) boolean /** * Delete value for key */ delete: (key: Key) void /** * Clear cache */ clear: () void }在源码中该接口名为UseMemoizeCacheindex.ts其五个方法分别对应 Map 的标准操作。一个简单的自定义实现示例如下import { useMemoize } from vueuse/core const localStorageCache { get: (key: string) { const raw localStorage.getItem(memo:${key}) return raw ? JSON.parse(raw) : undefined }, set: (key: string, value: unknown) { localStorage.setItem(memo:${key}, JSON.stringify(value)) }, has: (key: string) localStorage.getItem(memo:${key}) ! null, delete: (key: string) localStorage.removeItem(memo:${key}), clear: () { // 按前缀清理 }, } const getUser useMemoize( async (id: number) (await fetch(/users/${id})).json(), { cache: localStorageCache }, )从源码看传入的cache会被shallowReactive包裹index.ts因此自定义缓存同样具备响应式能力。测试用例 index.browser.test.ts 使用一组 vi.fn 模拟的缓存对象验证了get / set / has / delete / clear五个方法在get、load、delete、clear四条调用路径上的正确调用序列——例如命中缓存时只调用has和get未命中时先has再setload则无条件set。这些测试既是行为契约也是你实现自定义缓存时的最佳参考。内存泄漏的边界务必主动清理官方文档在开头就给出了一条重要提示缓存结果不会自动清除。当你不再需要这些结果时请调用clear()或使用自己的缓存机制如带 TTL 的缓存来避免内存泄漏。这是useMemoize最容易踩的坑因为缓存是永久的在长生命周期组件或全局作用域中如果参数是高基数的如不断增长的 ID、时间戳、随机字符串缓存 Map 会无限膨胀。应对策略包括在组件卸载时调用clear()配合tryOnUnmounted或onUnmounted在确定某个数据不再使用时调用delete(...args)精确清理注入带 TTL 或 LRU 淘汰策略的自定义缓存。源码级原理剖析一个只有 127 行的工具让我们完整走一遍 useMemoize 源码 的执行逻辑理解它为何如此轻量初始化缓存L75-L80若有options.cache则将其shallowReactive化否则shallowReactive(new Map())。选择shallowReactive而非deepReactive是有意为之——缓存值本身不需要深层响应式浅层即可触发依赖更新同时避免深代理带来的性能开销。生成缓存键L85-L88generateKey优先使用options.getKey否则回退到JSON.stringify(args)。读取命中L113-L119直接调用memoized(...args)时先cache.has(key)判断命中则返回cache.get(key)未命中则执行_loadData。强制刷新L93-L97loadData直接cache.set(key, resolver(...args))后从缓存取回结果不查命中。删除与清空L102-L111deleteData按生成的键删除单个条目clearData清空整个容器。组装返回对象L120-L126把load、delete、clear、generateKey、cache挂到可调用函数对象上。整个流程没有任何定时器、没有额外依赖追踪逻辑响应式完全由shallowReactive承担——这也是它体积仅有 242 B 的原因。使用注意事项小结响应式的前提缓存值的变化通过shallowReactive的 Map 触发因此必须让computed/computedAsync建立对cache的依赖追踪即在求值回调里调用 memoized 函数load之后视图才会更新。异步去重的边界官方文档提到它会复用已有 Promise 以避免同时重复获取数据。需要留意的是这里复用发生在同一参数并发调用时一旦某个 Promise 完成并写入缓存后续调用走的是缓存命中路径。缓存键的颗粒度默认JSON.stringify(args)对结构相等但引用不同的对象友好若参数复杂或体积大务必自定义getKey提升性能。清理是使用者的责任无自动过期机制请根据场景主动delete/clear或注入带淘汰策略的自定义缓存。类型安全useMemoizeResult, Args是泛型函数resolver 的参数与返回类型会被完整推导getKey的参数类型与 resolver 保持一致。参考链接官方文档useMemoize源码实现packages/core/useMemoize/index.ts测试用例packages/core/useMemoize/index.browser.test.tscomputedAsync 源码packages/core/computedAsync/index.ts体积统计packages/export-size.md赞分享前端【免费下载链接】vueuseCollection of essential Vue Composition Utilities for Vue 3项目地址https://gitcode.com/gh_mirrors/vu/vueuse点击查看免费下载相关推荐AirI 项目实战用 VueUse useMemoize 构建带缓存、响应式且防抖重复请求的函数记忆化方案AirI 项目实战用 VueUse useMemoize 构建带缓存、响应式且防抖重复请求的函数记忆化方案 本篇技术指南围绕 useMemoize 参考文档AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染VueUse useRouteParams 完全指南在 airi 的 Vue 3 应用中响应式读写路由参数VueUse useRouteParams 完全指南在 airi 的 Vue 3 应用中响应式读写路由参数 useRouteParams 是 VueUse 为AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染用 BabelDOC 把英文 PDF 译成中文同时保住公式和原版排版用 BabelDOC 把英文 PDF 译成中文同时保住公式和原版排版 一份一百多页的英文论文发过来要求下周前给出中文版——逐段复制到网页翻译器公式乱序、表人工智能AI 应用NLP计算机视觉上一篇抖音下载神器如何一键保存无水印视频与批量下载用户作品下一篇抖音无水印下载器终极免费批量下载工具完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →