缓存 localStorage 与 Cookie 读取:Plate 仓库中的 js-cache-storage 存储性能优化实践

缓存 localStorage 与 Cookie 读取:Plate 仓库中的 js-cache-storage 存储性能优化实践 缓存 localStorage 与 Cookie 读取Plate 仓库中的 js-cache-storage 存储性能优化实践【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate在富文本编辑器等客户端应用里主题偏好、常用表情、插件配置常常存放在localStorage、sessionStorage和document.cookie中而这些 Storage API 是同步调用高频读取会造成不必要的主线程开销。本文基于 Plate 仓库中集成的 Vercel React 性能规则文件 js-cache-storage.md完整讲解把存储读取缓存在内存中的写法Map 缓存 写时同步 失效监听并结合仓库中真实存在的主题切换、表情存储等代码说明这条规则在 Plate 项目里的适用位置与落地方式。规则定位来自 Vercel React 性能规则集的 js-cache-storage该规则文档位于 Plate 仓库的 vercel-react-best-practices 技能目录 下。从 SKILL.md 可以看到这套规则集包含 69 条规则、8 个类别按影响程度分级其中js-cache-storage描述为 Cache localStorage/sessionStorage reads归属于JavaScript Performancejs-前缀类别影响级别为LOW-MEDIUM元数据标注其收益为 reduces expensive I/O减少昂贵的 I/O。规则本身的核心论断只有一句话localStorage、sessionStorage和document.cookie都是同步且昂贵的。把读取结果缓存在内存里。同步意味着每次调用都会阻塞当前执行包括字符串解析、存储引擎内部序列化等开销昂贵则意味着即便单次元数据不大调用次数多了累计成本也不可忽略——例如一个工具函数每渲染周期被调用 10 次就会产生 10 次存储读取。反模式每次调用都读存储规则文档给出的错误示例是一个典型的每次调用都读存储的写法function getTheme() { return localStorage.getItem(theme) ?? light } // Called 10 times 10 storage reads问题在于函数本身没有任何记忆调用方无法预知该值在页面生命周期内几乎不会变除非别的 tab 修改了它于是每次渲染、每次事件回调都会触发一次真实的存储读取。正确写法模块级 Map 缓存规则给出的正确实现是懒加载 Map 缓存const storageCache new Mapstring, string | null() function getLocalStorage(key: string) { if (!storageCache.has(key)) { storageCache.set(key, localStorage.getItem(key)) } return storageCache.get(key) } function setLocalStorage(key: string, value: string) { localStorage.setItem(key, value) storageCache.set(key, value) // keep cache in sync }这里有两个关键设计点首次读取才落盘。getLocalStorage第一次遇到某个 key 时执行真实getItem之后全部命中内存 Map把 N 次同步 I/O 压缩为 1 次。写时同步write-through。setLocalStorage在写真实存储的同时立即更新storageCache保证缓存与持久层一致后续读取不会拿到过期值。文档还特别强调要用 Map 而不是 React HookUse a Map (not a hook) so it works everywhere。原因是存储读取往往发生在工具函数、事件处理器、非组件代码中Hook 只能在组件或自定义 Hook 内调用而模块级 Map 在任何上下文包括纯 TS 工具模块都可以安全使用适用范围更广。Cookie 的缓存一次性解析整个 cookie 串document.cookie的特殊之处在于它每次访问返回的是整个 cookie 字符串取某个 key 还要手动split。因此规则建议首次访问时把整个 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] }注意这里是惰性初始化整个缓存null判定 一次性Object.fromEntries解析而不是逐个 key 解析。这样 N 次getCookie调用只做 1 次字符串解析。需要说明其适用边界服务端下发的 cookie 变化不会触发本缓存的失效因此这段缓存只适合页面生命周期内 cookie 基本只读的场景。缓存一致性在外部变更时主动失效规则文档的最后一节标记为Important处理的是一个容易被忽略的问题存储可能在本 tab 之外被修改。文档原话If storage can change externally (another tab, server-set cookies), invalidate cache.如果存储可能被外部修改——另一个 tab、服务端设置的 cookie——就要让缓存失效。给出的两个监听器window.addEventListener(storage, (e) { if (e.key) storageCache.delete(e.key) }) document.addEventListener(visibilitychange, () { if (document.visibilityState visible) { storageCache.clear() } })storage事件其他文档/标签页修改存储后storage事件会在本页面触发用它精准失效对应 key。注意e.key可能为null对应整库移除的场景所以代码里用if (e.key)做了保护visibilitychange兜底处理用户切回页签的时刻——只要回到前台就clear()全量重建缓存用极小的重复读取成本换取缓存与真实存储的强一致覆盖跨 tab 修改、隐私模式限制等storage事件无法完全捕获的情况。规则在 Plate 仓库中的真实落点这条规则虽然分级为 LOW-MEDIUM但 Plate 仓库一个基于 Slate 的富文本编辑器框架文档站基于 Next.js中确实存在多处与规则示例同构的存储访问代码可以作为落地参照。主题偏好getTheme场景的直接对应theme-provider.tsx 正是规则文档中getTheme()示例的现实版——它基于next-themes提供主题上下文并在挂载后把localStorage中的主题同步写入 cookietheme-provider.tsx#L47-L51React.useEffect(() { // Sync initial theme to cookie const theme localStorage.getItem(theme) || system; document.cookie theme${theme};path/;max-age31536000; }, []);这里同时出现了规则讨论的两个 APIlocalStorage.getItem读取与document.cookie写入。由于当前写法只在 effect 中读一次开销本身可控但若后续出现多处组件各自读取theme的场景就应套用本文的 Map 缓存模式统一收口避免重复读取。emoji 包典型的每次 get 都读存储实现packages/emoji 的 LocalStorage 类 是一个泛型的 localStorage 封装get()方法每次都执行真实的getItem加JSON.parseLocalStorage.ts#L15-L31get(): T { let value this.defaultValue; if (typeof window undefined) return value; const valueInLocalStorage window.localStorage.getItem(this.key); if (valueInLocalStorage) { try { value JSON.parse(valueInLocalStorage); } catch { window.localStorage.removeItem(this.key); } } return value; }从源码结构看这个实现自带了规则文档未展开的两个工程细节恰好补充了规则的边界条件SSR 保护typeof window undefined时直接返回默认值避免在 Next.js 服务端渲染阶段访问浏览器 API脏数据自愈JSON.parse失败时直接removeItem清掉损坏值防止脏数据反复触发异常。如果按js-cache-storage规则增强它只需在实例或模块级 Map上叠加一层首次getItem后缓存解析结果的读写通道get()的其余逻辑SSR 保护、JSON 容错保持不变——这正是规则 reduce expensive I/O 的意图。读改写热点常用表情统计的 update 路径FrequentEmojiStorage 在用户每次选择表情时都会执行一次读—改—写FrequentEmojiStorage.ts#L45-L57update(emojiId: string) { const prevEmojis this.localStorage.get(); const count prevEmojis![emojiId] ? prevEmojis[emojiId] 1 : 1; const emojis: FrequentEmojis { ...prevEmojis, [emojiId]: count, }; this.localStorage.set(emojis); return emojis; }由于底层get()每次都读真实存储高频选择表情意味着高频的同步 I/O。这里与规则中写时同步缓存的组合收益更直接如果LocalStorage内部维护了内存副本update的读步骤可以直接命中缓存只有set落盘一次。另外useCalloutEmojiPicker 在用户选择 callout 图标后执行localStorage.setItem(CALLOUT_STORAGE_KEY, icon)useCalloutEmojiPicker.ts#L42属于纯写入路径配合带同步能力的缓存写入函数即可保证后续读取的一致性。落地自检清单结合规则文档与 Plate 仓库中的实际用法可以按以下清单自查是否把localStorage.getItem/document.cookie放进了会被高频调用的工具函数或渲染路径是则引入 Map 缓存写入路径是否统一走真实存储 缓存同步的成对函数避免缓存与持久层漂移该值是否可能被其他 tab 或服务端修改是则挂storage事件做单 key 失效并用visibilitychange回到前台时全量失效兜底SSR 环境是否已加typeof window undefined保护参考 LocalStorage.ts 的写法是否用模块级 Map 而非 Hook 实现缓存以覆盖工具函数与事件处理器等非组件场景小结js-cache-storage规则给出的方案非常克制一个模块级 Map 解决读的重复 I/O写时同步保证一致性storage/visibilitychange两个事件监听处理外部变更失效。Plate 仓库中的主题同步theme-provider.tsx与 emoji 存储封装LocalStorage.ts展示了这类代码在真实 Next.js React 项目中的分布形态读路径值得缓存写路径需要同步边界场景SSR、脏 JSON、跨 tab 修改需要显式处理。这套模式对所有在客户端持久化用户偏好的组件库与站点都通用。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考