ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Phoenix 前端性能优化:用模块级 Map 缓存重复函数调用(js-cache-function-results 规则实战解析)

Phoenix 前端性能优化:用模块级 Map 缓存重复函数调用(js-cache-function-results 规则实战解析) 可观测性AI 评测LLMOpsAI 应用人工智能【免费下载链接】phoenixAI Observability Evaluation项目地址https://gitcode.com/gh_mirrors/phoenix13/phoenix点击查看免费下载本篇技术指南围绕开源仓库 phoenix 中.agents/skills/vercel-react-best-practices技能包内js-cache-function-results规则展开讲解在 React/Next.js 应用中如何用模块级Map缓存相同输入下的重复函数调用避免渲染期间的冗余计算。读完本文你将掌握模块级缓存的标准写法、单值缓存简化模式、缓存失效策略与容量上限控制并能看到该模式在 Phoenix 前端js/app中的真实落地实现。规则速览它解决什么问题规则文件 js-cache-function-results.md 属于vercel-react-best-practices技能包中的JavaScript PerformanceLOW-MEDIUM 优先级类别其元数据定义如下titleCache Repeated Function CallsimpactMEDIUMimpactDescriptionavoid redundant computation避免冗余计算tagsjavascript, cache, memoization, performance根据技能包总览 SKILL.md该技能包收录了 Vercel Engineering 维护的 70 条规则横跨 8 大类别。js-前缀规则共 14 条聚焦纯 JavaScript 层面的性能细节与js-index-maps用 Map 做重复查找索引、js-cache-property-access循环内缓存属性访问、js-cache-storage缓存 localStorage/sessionStorage 读取等规则同属一个主题族本规则是其中缓存函数计算结果的一条。核心要义一句话当同一函数在渲染期间以相同输入被反复调用时用模块级Map缓存其结果让每个唯一输入只计算一次。反模式渲染循环中的重复计算规则给出的典型反模式是在组件渲染的map循环里直接调用昂贵函数相同的输入被重复计算。function ProjectList({ projects }: { projects: Project[] }) { return ( div {projects.map(project { // slugify() called 100 times for same project names const slug slugify(project.name) return ProjectCard key{project.id} slug{slug} / })} /div ) }问题在于slugify(project.name)是纯函数——相同名称必然产生相同 slug但这里对重复出现的项目名会反复执行字符串处理逻辑正则替换、URL 编码、归一化等。当项目列表很大比如 100 条记录里大量重名、或者列表在多次渲染中反复出现时这些计算被无意义地重复执行。React 渲染本身是函数执行每次 state 更新都会重新执行整个组件函数体因此渲染路径上的重复计算会随渲染次数放大而不是只发生一次。正确模式模块级 Map 缓存规则给出的修复方案是将缓存提升到模块作用域// Module-level cache const slugifyCache new Mapstring, string() function cachedSlugify(text: string): string { if (slugifyCache.has(text)) { return slugifyCache.get(text)! } const result slugify(text) slugifyCache.set(text, result) return result } function ProjectList({ projects }: { projects: Project[] }) { return ( div {projects.map(project { // Computed only once per unique project name const slug cachedSlugify(project.name) return ProjectCard key{project.id} slug{slug} / })} /div ) }这套查缓存 → 命中即返回 → 未命中则计算并写入的三段式结构是手写 memoization 的标准形态其本质是为纯函数建立一个以输入为键、以输出为值的查找表Map按引用比较键对字符串、对象等引用类型都能精确命中每个唯一输入只计算一次后续调用退化为 O(1) 的哈希查找缓存生命周期与模块一致跨渲染、跨组件实例共享。Phoenix 前端代码中就有完全同构的实现。以混淆矩阵工具函数 confusionMatrixUtils.ts 为例它用模块级 Map 缓存填充色 → 墨水色的映射const inkByFillColor new Mapstring, string(); function getInkColor(backgroundColor: string): string { let ink inkByFillColor.get(backgroundColor); if (ink null) { try { ink getLuminance(backgroundColor) DARK_INK_LUMINANCE_THRESHOLD ? DARK_INK : LIGHT_INK; } catch { ink var(--global-text-color-900); } inkByFillColor.set(backgroundColor, ink); } return ink; }源码注释明确写明了动机getLuminance每次调用都会重新解析颜色字符串而矩阵渲染反复询问同一小撮填充色缓存被矩阵能产出的不同颜色数量天然限定。这正好印证了本规则的核心主张——当输入空间远小于调用次数时模块级 Map 缓存收益最明显。另一个例子是 Shimmer.tsx 中的motionComponentCache// motion.create(tag) is pure per tag — cache so we dont rebuild the component // on every render of Shimmer. const motionComponentCache new MapIntrinsicTag, MotionTagComponent(); const getMotionComponent (element: IntrinsicTag): MotionTagComponent { const cached motionComponentCache.get(element); if (cached) return cached; const component motion.create(element) as MotionTagComponent; motionComponentCache.set(element, component); return component; };这里缓存的是每次 render 都会重建、但结果按 tag 恒定的组件对象同样的三段式结构注释里pure per tag与规则中相同输入相同输出的前提完全一致。单值缓存更简单的简化模式对于函数只返回一个值的场景规则提供了更轻量的简化模式——不需要Map一个模块级可空变量即可let isLoggedInCache: boolean | null null function isLoggedIn(): boolean { if (isLoggedInCache ! null) { return isLoggedInCache } isLoggedInCache document.cookie.includes(auth) return isLoggedInCache } // Clear cache when auth changes function onAuthChange() { isLoggedInCache null }这个模式的要点用null作为尚未缓存的哨兵值因此缓存值本身不能是null否则会退化读取document.cookie这类同步且昂贵的 I/O 只执行一次失效逻辑显式化onAuthChange()把缓存复位为null下一次调用重新计算。这体现了 memoization 最重要的配套动作——缓存失效invalidation必须与数据变更事件挂钩否则缓存会成为 bug 的来源。为什么用 Map 而不是 Hook规则特别强调用Map不要用 Hook这样缓存才能到处可用——工具函数、事件处理器、普通模块中都能工作而不局限于 React 组件。这一点在架构层面很有价值useMemo/useRef的缓存生命周期绑定在组件实例上组件卸载即失效无法跨实例共享模块级缓存的生命周期与 JS 模块一致天然适合与 UI 无关的纯计算把缓存下沉到纯函数层还能让组件代码保持干净——组件只负责调用cachedSlugify不关心缓存细节。当然这也意味着模块级缓存不会随组件卸载自动清理因此只适用于输入空间有限、输出稳定的纯函数如 slug 化、颜色计算、正则编译并且需要配合失效策略见下文。缓存失效与外部变更保持同步模块级缓存最大的风险是陈旧数据stale data。规则在同族的 js-cache-storage.md 中给出了针对存储类缓存的完整失效方案window.addEventListener(storage, (e) { if (e.key) storageCache.delete(e.key) }) document.addEventListener(visibilitychange, () { if (document.visibilityState visible) { storageCache.clear() } })这里覆盖了两个典型的外部变更来源跨标签页变更storage事件由其他标签页对 localStorage 的写入触发收到事件即删除对应键的缓存后台恢复页面从后台切回前台visibilitychange到visible时直接整体清空缓存强制重新读取。对于document.cookie同文件给出的缓存写法是整段解析一次、整体缓存let cookieCache: Recordstring, string | null null function getCookie(name: string) { if (!cookieCache) { cookieCache Object.fromEntries( document.cookie.split(; ).map(c c.split()) ) } return cookieCache[name] }失效策略的通用原则只要底层数据可能在缓存作用域之外被修改服务端下发 cookie、其他标签页写存储、登录态变化就必须为缓存注册对应的失效钩子能精确到键就删键无法精确就整体清空。规则的onAuthChange()单值缓存复位正是同一原则在业务层的体现。进阶缓存 Promise 与控制缓存上限模块级 Map 的另一个强大用法是缓存异步结果Promise——Phoenix 的 span 过滤器校验模块 spanFilterValidation.ts 提供了教科书式的实现const MAX_VALIDATION_CACHE_ENTRIES 100; const validationCache new Map string, PromiseSpanFilterConditionValidation (); function validationCacheKey(projectId: string, condition: string) { return JSON.stringify([projectId, condition]); } function readCachedValidation(key: string) { const cached validationCache.get(key); if (cached) { // Refresh insertion order so the least recently used entry is evicted. validationCache.delete(key); validationCache.set(key, cached); } return cached; } function cacheValidation(key, validation) { validationCache.set(key, validation); if (validationCache.size MAX_VALIDATION_CACHE_ENTRIES) { const leastRecentlyUsedKey validationCache.keys().next().value; if (leastRecentlyUsedKey ! undefined) { validationCache.delete(leastRecentlyUsedKey); } } }这个实现展示了两个值得借鉴的进阶设计缓存 Promise 而非结果值同一过滤条件被并发触发时第二次调用直接复用第一个 Promise避免重复发起服务器请求请求去重LRU 风格容量上限MAX_VALIDATION_CACHE_ENTRIES 100每次命中时先删除再重新插入刷新插入顺序超出上限时从keys().next().value淘汰最久未用的条目——用 Map 的迭代顺序天然实现了简单的 LRU 淘汰。这一点弥补了模块级缓存不随组件卸载清理的短板当输入空间不可控如用户可任意输入的 DSL 表达式时必须为缓存设置容量上限防止缓存无限膨胀造成内存泄漏。权衡边界什么时候不该用缓存任何优化都有适用边界本规则亦然。结合源码实践可以归纳出以下判断标准前提是纯函数相同输入必须保证相同输出。getLuminance、motion.create(tag)、slugify都满足而依赖随机数、当前时间、外部可变状态的函数绝不能缓存输入空间要有限inkByFillColor缓存由插值器产生的有限颜色集合motionComponentCache缓存有限的 HTML 标签集合这类场景缓存几乎总是命中若输入是无限增长的如用户输入的任意字符串必须像validationCache那样加上限计算成本要可观Map.hasMap.get本身也有开销对于字符串拼接这种极廉价操作缓存收益趋近于零规则的impact评级为 MEDIUM 而非 CRITICAL也说明它属于值得做但不必优先的优化失效成本要可控凡底层数据会外部变更都必须评估能否低成本挂接失效钩子做不到就别缓存。与同族规则协同完整的前端性能优化视角模块级函数结果缓存不是孤立的技巧它与同族的 JS 性能规则互补共同构成减少冗余计算的完整工具箱js-index-maps.md多个.find()按同一键查找时先构建一次Map索引将 O(n) 查找降为 O(1)1000 订单 × 1000 用户的场景从 100 万次操作降到 2 千次与本规则以 Map 换时间的思路一脉相承js-cache-property-access.md循环体内把obj.config.settings.value这类深层属性访问提前到循环外缓存减少每次迭代的属性查找js-cache-storage.mdlocalStorage / sessionStorage / cookie 都是同步且昂贵的 I/O缓存到内存并处理失效与本规则共享同一套模块级缓存 失效范式。在 SKILL.md 的优先级排序中js-类别位于第 7 位LOW-MEDIUM属于在消除瀑布流、优化 bundle、服务端性能等 CRITICAL/HIGH 级问题处理完之后再着手的优化层。实践中的合理顺序是先保证架构层面的数据获取与渲染路径没有根本性问题再用本规则打磨高频计算热点。小结模块级Map缓存重复函数调用是性价比极高的前端性能优化手段用一次 O(1) 的查找替换掉反复执行的昂贵计算。落地时只需记住三个要点只缓存纯函数且输入空间有限显式管理失效——数据变更处必须挂钩子复位缓存外部可能变更时按storage/visibilitychange事件处理为不可控的输入空间设容量上限参考spanFilterValidation.ts的 LRU 淘汰模式。Phoenix 前端在 confusionMatrixUtils.ts、Shimmer.tsx、spanFilterValidation.ts 三处的真实实现分别示范了有限输入缓存、纯函数组件对象缓存、Promise LRU 上限缓存三种变体可直接作为实现参考。该规则的完整定义与反/正例请以 js-cache-function-results.md 为准其思路源自 Vercel 官方博客《How we made the Vercel Dashboard twice as fast》公开的性能优化实践。赞分享可观测性AI 评测LLMOpsAI 应用人工智能【免费下载链接】phoenixAI Observability Evaluation项目地址https://gitcode.com/gh_mirrors/phoenix13/phoenix点击查看免费下载相关推荐AutoGPT 前端性能守则用模块级 Map 缓存重复函数调用js-cache-function-results 规则详解AutoGPT 前端性能守则用模块级 Map 缓存重复函数调用js cache function results 规则详解 本文详解 AutoGPT 仓库人工智能AI Agent自主智能体Agent 工作流工作流自动化后端前端Actions Runner Images项目中macOS构建失败的深度分析Actions Runner Images项目中macOS构建失败的深度分析 问题背景 在软件开发持续集成过程中使用Actions Runner Images后端前端CRM人工智能AI AgentSanity 性能规则精讲用模块级 Map 缓存重复函数调用结果js-cache-function-resultsSanity 性能规则精讲用模块级 Map 缓存重复函数调用结果js cache function results 本文基于 Sanity 仓库内置的 VCMS前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进