深入理解 VueUse `useStorage`:为 Vue 3 应用打造响应式 Web Storage 状态层

发布时间:2026/9/10 13:20:26
深入理解 VueUse `useStorage`:为 Vue 3 应用打造响应式 Web Storage 状态层 深入理解 VueUseuseStorage为 Vue 3 应用打造响应式 Web Storage 状态层【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi导读useStorage是 VueUse 状态State类别中用于把 Vue 响应式ref与浏览器localStorage/sessionStorage双向绑定的核心组合式函数。在 airi 这类横跨 Web、PWACapacitor与 Electron 桌面的多端应用中它被大量用于持久化设置项并进一步被封装为带版本校验、手动重置能力的本地存储抽象。读完本文你将掌握useStorage的完整调用姿势、默认值与序列化机制、全部配置项的含义以及如何借鉴 airi 仓库中的版本化封装为生产级应用设计可靠的本地持久化方案。useStorage创建的是一个可以直接读写、自动同步存储介质的响应式引用reactive ref默认绑定localStorage也可通过第三个参数指定sessionStorage或其他StorageLike对象。本文以仓库内 useStorage.md 为骨架结合 airi 源码中的真实封装与测试展开讲解。基本用法一行代码把响应式状态落到浏览器存储useStorage最直观的价值在于你无需再手动getItem/setItem 监听事件来同步状态它根据传入默认值的类型自动选择序列化方式并返回一个带类型的RemovableRef。import { useStorage } from vueuse/core // 绑定对象JSON 序列化 const state useStorage(my-store, { hello: hi, greeting: Hello }) // 绑定布尔值返回 Refboolean const flag useStorage(my-flag, true) // 绑定数字返回 Refnumber const count useStorage(my-count, 0) // 绑定字符串并指定 sessionStorage返回 Refstring const id useStorage(my-id, some-string-id, sessionStorage) // 删除存储中的数据 state.value null几点值得注意删除数据把state.value赋值为null会调用存储介质的removeItem这正是RemovableRef语义的体现。存储介质可替换第三个参数只要是满足getItem/setItem/removeItem接口的对象即可因此useStorage天然支持单元测试中注入内存存储替身——airi 的测试里就是这么做的见下文。返回值带类型不同默认值类型会命中不同的函数重载返回Refboolean、Refnumber、Refstring或RefT。Nuxt 3 使用提示当在 Nuxt 3 中使用时该函数不会被自动导入以免与 Nitro 内置的同名useStorage()冲突。若你确实要使用 VueUse 版本请显式import { useStorage } from vueuse/core。airi 仓库的所有应用如 apps/stage-pocket/package.json、apps/stage-tamagotchi/package.json、apps/component-calling/package.json也都是通过显式声明vueuse/core依赖catalog 版本管理来使用这套 API 的。默认值合并策略Merge Defaults避免新增字段变成undefined默认情况下只要存储里已存在该 key 的值useStorage就会直接使用存储值而忽略默认值。这意味着当你给默认对象新增属性时老用户存储中没有这个 key 的对应字段读取结果会是undefinedimport { useStorage } from vueuse/core localStorage.setItem(my-store, {hello: hello}) const state useStorage(my-store, { hello: hi, greeting: hello }, localStorage) console.log(state.value.greeting) // undefined因为存储中没有该字段要解决这种存储值落后于代码默认值的问题可以开启mergeDefaults选项import { useStorage } from vueuse/core localStorage.setItem(my-store, {hello: nihao}) const state useStorage( my-store, { hello: hi, greeting: hello }, localStorage, { mergeDefaults: true }, // -- 开启合并 ) console.log(state.value.hello) // nihao来自存储 console.log(state.value.greeting) // hello来自合并进来的默认值合并规则说明mergeDefaults: true时对对象执行浅合并shallow merge存储中已有的字段以存储值为准默认值中新增的字段被补入。也可以传入自定义合并函数例如实现深合并import { useStorage } from vueuse/core const state useStorage( my-store, { hello: hi, greeting: hello }, localStorage, { mergeDefaults: (storageValue, defaults) deepMerge(defaults, storageValue) }, )仓库实践版本化合并的思路升级airi 在 packages/stage-shared/src/composables/use-versioned-local-storage/index.ts 中把合并默认值升级为版本化存储写入 localStorage 的值统一包装为{ version, data }结构每次读取时用satisfiesVersionBy回调比较存储版本与当前defaultVersion不满足版本要求时走onVersionMismatch策略keep保留旧值或reset重置为默认值从而以声明方式解决代码升级后旧数据不兼容的问题。其测试 use-versioned-local-storage/index.test.ts 用一个MemoryStorage仅实现getItem/setItem/removeItem的最小StorageLike验证了写入settings/live2d/auto-blink-enabled时会持久化为{ version: 2.0.0, data: false }的包装结构——这也是useStorage支持自定义存储介质这一设计带来的直接收益。自定义序列化Custom Serialization从 JSON 到 Map / Set / DateuseStorage会根据默认值类型智能挑选序列化器对象走JSON.stringify/JSON.parse数字走Number.toString/parseFloat等等。你也可以完全接管序列化过程import { useStorage } from vueuse/core useStorage( key, {}, undefined, { serializer: { read: (v: any) v ? JSON.parse(v) : null, write: (v: any) JSON.stringify(v), }, }, )需要注意当默认值为null时useStorage无法从类型推断序列化方式此时应显式提供自定义序列化器或复用内置序列化器import { StorageSerializers, useStorage } from vueuse/core const objectLike useStorage(key, null, undefined, { serializer: StorageSerializers.object }) objectLike.value { foo: bar }内置序列化器一览StorageSerializersStorageSerializers提供了以下开箱即用的序列化器类型说明string普通字符串原样读写number数字经parseFloat读取boolean布尔值objectJSON 对象/数组mapJavaScriptMapsetJavaScriptSetdateJavaScriptDate经toISOString写入any原始字符串直通例如把Map持久化到存储import { StorageSerializers, useStorage } from vueuse/core const myMap useStorage(my-map, new Map(), undefined, { serializer: StorageSerializers.map, })Options 完整配置项useStorage的第四个参数接受UseStorageOptionsT完整的调用形态与注释如下useStorage(key, defaults, storage, { // 深度监听对象/数组内部变化默认 true deep: true, // 通过 storage 事件跨标签页同步默认 true listenToStorageChanges: true, // 存储中不存在时把默认值写入存储默认 true writeDefaults: true, // 使用 shallowRef 而非 ref默认 false shallow: false, // 仅在组件挂载后再初始化读取默认 false initOnMounted: false, // 自定义错误处理默认 console.error onError: e console.error(e), // watch 刷新时机默认 pre flush: pre, })各选项的底层影响deep决定内部watch是否深度跟踪对象/数组的嵌套变化进而决定嵌套字段修改时是否触发回写。listenToStorageChanges监听storage事件实现多标签页同步。开启时跨标签页的修改会实时反映到当前页面airi 的封装中该选项同样被透传见 use-local-storage-manual-reset/index.ts其中options?.listenToStorageChanges ! false时才同步存储来源的变更。writeDefaults首次访问时若存储中无此 key把默认值写入存储避免下次读取时拿到null。shallow对大型/复杂对象可减少深层响应式开销。initOnMounted延迟到onMounted后再读取存储适合 SSR 场景避免在服务端访问window.localStorage。onError统一接管解析失败、写入异常等错误便于接入上报。flush沿用 Vuewatch的 flush 语义pre/post/sync决定回写时机。Reactive Key让存储键本身可响应存储键可以是ref或 getter 函数当 key 变化时useStorage会从新的存储位置读取数据import { useStorage } from vueuse/core const userId ref(user-1) const userData useStorage( () user-data-${userId.value}, { name: }, ) // 切换 key 后将从新的存储位置读取 userId.value user-2这一特性非常适合多实例状态各自持久化的场景例如按会话、按角色、按账号维度隔离本地数据。仓库中的组合式实践useLocalStorageManualResetairi 在 packages/stage-shared/src/composables/use-local-storage-manual-reset/index.ts 中把useLocalStorage即固定为localStorage的useStorage便捷封装与refManualReset组合构造出可手动重置的本地存储 ref用useLocalStorageT(key, value, options)建立持久化层外层用refManualReset包装暴露reset()语义通过双向 watch 在用户写入与存储来源变更之间桥接并利用toRaw比较避免同值回写引发的二次 Pinia 变更循环见源码注释。该封装被实际用于 packages/stage-ui/src/stores/settings/general.ts 的 Pinia store 中持久化settings/language、settings/disable-transitions、settings/websocket/secure-enabled等设置项并提供了resetState()一键恢复默认值的能力const language useLocalStorageManualResetstring(settings/language, ) const disableTransitions useLocalStorageManualResetboolean(settings/disable-transitions, true) function resetState() { language.reset() disableTransitions.reset() // ... }这类ref storage 手动重置的组合正是useStorage在真实项目中作为基础构件被二次封装、融入 Pinia 状态管理的典型范式。类型声明与扩展接口useStorage相关的完整类型声明来自 useStorage.md如下它揭示了自定义序列化器与事件过滤等扩展点export interface SerializerT { read: (raw: string) T write: (value: T) string } export interface SerializerAsyncT { read: (raw: string) AwaitableT write: (value: T) Awaitablestring } export declare const StorageSerializers: Record boolean | object | number | any | string | map | set | date, Serializerany export declare const customStorageEventName vueuse-storage export interface StorageEventLike { storageArea: StorageLike | null key: StorageEvent[key] oldValue: StorageEvent[oldValue] newValue: StorageEvent[newValue] } export interface UseStorageOptionsT extends ConfigurableEventFilter, ConfigurableWindow, ConfigurableFlush { deep?: boolean // 深度监听默认 true listenToStorageChanges?: boolean // 监听 storage 变化默认 true writeDefaults?: boolean // 写入默认值默认 true mergeDefaults?: boolean | ((storageValue: T, defaults: T) T) // 合并默认值默认 false serializer?: SerializerT // 自定义序列化 onError?: (error: unknown) void // 错误回调默认 console.error shallow?: boolean // 使用 shallowRef默认 false initOnMounted?: boolean // 挂载后初始化默认 false }重载签名覆盖了string/boolean/number/ 泛型T/null五种形态其中defaults: null时返回RemovableRefT配合显式serializer使用。此外它还继承了ConfigurableEventFilter、ConfigurableWindow、ConfigurableFlush三个可配置接口分别用于事件过滤如eventFilter、窗口对象注入如 SSR 中的window与 watch 刷新时机控制这为在非浏览器环境如 Node 测试、Electron 主进程复用该 API 提供了统一入口。结语useStorage用极小的 API 面覆盖了 Web Storage 持久化的全部关键诉求类型化响应式绑定、智能序列化、默认值合并、多标签页同步、可响应 key 与可插拔存储介质。airi 仓库中的 use-versioned-local-storage、use-local-storage-manual-reset 及其配套测试展示了如何在其之上构建版本兼容与手动重置等生产级能力可作为你在自己的 Vue 3 项目中设计本地持久化层的直接参考。如果你还需要异步存储如 IndexedDB、自定义异步后端可以继续阅读同一 skill 目录下的 useStorageAsync.md 与 useLocalStorage.md、useSessionStorage.md 等姊妹文档。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考