Vue3/React项目实战:如何优雅地集成带过期时间的LocalStorage封装库?
Vue3/React项目实战:如何优雅地集成带过期时间的LocalStorage封装库?
在现代前端开发中,数据缓存是提升用户体验的关键技术之一。LocalStorage作为浏览器提供的持久化存储方案,因其简单易用而广受欢迎。然而,原生LocalStorage缺乏自动过期机制,这在需要管理登录Token、临时表单数据等场景下显得尤为不便。本文将深入探讨如何在Vue3和React项目中,以框架原生思维集成带过期时间的LocalStorage封装方案,实现更优雅的缓存管理。
1. 核心封装原理与TypeScript实现
任何优秀的工具库都始于清晰的设计思路。我们首先需要理解带过期时间的LocalStorage核心机制:
// 定义存储数据结构 interface StorageData<T> { value: T _expire: number | 'permanent' } class SmartStorage { set<T>(key: string, value: T, expire: number | 'permanent' = 'permanent') { const data: StorageData<T> = { value, _expire: expire === 'permanent' ? 'permanent' : Date.now() + expire * 1000 } localStorage.setItem(key, JSON.stringify(data)) } get<T>(key: string): T | null { const raw = localStorage.getItem(key) if (!raw) return null const data = JSON.parse(raw) as StorageData<T> if (data._expire !== 'permanent' && data._expire < Date.now()) { this.remove(key) return null } return data.value } }这种实现有几个关键优势:
- 类型安全:通过TypeScript泛型确保存取数据时类型一致
- 灵活过期:支持永久存储和相对时间过期(秒级精度)
- 自动清理:读取时自动检查并清除过期数据
性能考量:虽然每次读取都有JSON解析开销,但对于大多数应用场景来说微不足道。对于高频访问的数据,建议配合内存缓存使用。
2. Vue3深度集成方案
2.1 作为插件全局注册
Vue3的插件系统允许我们优雅地扩展应用能力:
// storage-plugin.ts import { App } from 'vue' export default { install(app: App) { const storage = new SmartStorage() app.provide('smartStorage', storage) app.config.globalProperties.$storage = storage } } // main.ts import storagePlugin from './plugins/storage-plugin' createApp(App) .use(storagePlugin) .mount('#app')使用方式对比:
| 使用场景 | 组件内使用 | Composition API使用 |
|---|---|---|
| 传统方式 | this.$storage | const storage = inject('smartStorage') |
| 推荐方式 | - | useStorage()组合式函数 |
2.2 组合式函数封装
更符合Vue3设计哲学的方式是创建useStorage组合式函数:
// composables/useStorage.ts import { inject } from 'vue' export function useStorage() { const storage = inject('smartStorage') as SmartStorage const setWithExpire = <T>(key: string, value: T, expire?: number) => { storage.set(key, value, expire) } const getWithCheck = <T>(key: string): T | null => { return storage.get<T>(key) } return { set: setWithExpire, get: getWithCheck } }业务示例 - 用户Token管理:
// useAuth.ts export function useAuth() { const { set, get } = useStorage() const TOKEN_KEY = 'auth_token' const saveToken = (token: string) => { // 设置1小时过期 set(TOKEN_KEY, token, 3600) } const getValidToken = () => { return get<string>(TOKEN_KEY) } return { saveToken, getValidToken } }2.3 与Pinia状态管理集成
在大型项目中,我们可以将存储逻辑与状态管理结合:
// stores/auth.ts import { defineStore } from 'pinia' import { useStorage } from '../composables/useStorage' export const useAuthStore = defineStore('auth', { state: () => ({ token: null as string | null }), actions: { initFromStorage() { const { get } = useStorage() this.token = get('auth_token') }, persistToken(token: string) { const { set } = useStorage() set('auth_token', token, 3600) this.token = token } } })3. React生态集成方案
3.1 自定义Hook实现
React的函数式组件最适合通过自定义Hook来封装存储逻辑:
// hooks/useStorage.ts import { useEffect, useState } from 'react' const storage = new SmartStorage() export function useLocalStorage<T>( key: string, initialValue: T, expire?: number ): [T, (value: T) => void] { const [storedValue, setStoredValue] = useState<T>(() => { const item = storage.get<T>(key) return item ?? initialValue }) const setValue = (value: T) => { storage.set(key, value, expire) setStoredValue(value) } // 监听storage事件实现跨标签页同步 useEffect(() => { const handleStorage = (e: StorageEvent) => { if (e.key === key) { setStoredValue(storage.get<T>(key) ?? initialValue) } } window.addEventListener('storage', handleStorage) return () => window.removeEventListener('storage', handleStorage) }, [key, initialValue]) return [storedValue, setValue] }表单草稿保存示例:
function DraftForm() { const [draft, setDraft] = useLocalStorage( 'form_draft', { title: '', content: '' }, 86400 // 24小时自动过期 ) // ...表单逻辑 }3.2 与Redux中间件集成
对于使用Redux的项目,可以创建存储中间件:
const storageMiddleware = (store) => (next) => (action) => { const result = next(action) if (action.meta?.persist) { const state = store.getState() storage.set( action.meta.persistKey, state[action.meta.stateSlice], action.meta.expire ) } return result } // Action使用示例 const loginSuccess = (token) => ({ type: 'auth/login', payload: token, meta: { persist: true, persistKey: 'auth_token', stateSlice: 'auth', expire: 3600 } })4. 高级场景与性能优化
4.1 SSR兼容方案
在Next.js/Nuxt.js等SSR框架中,直接访问LocalStorage会导致服务端报错。解决方案:
class SafeStorage { private isServer: boolean constructor() { this.isServer = typeof window === 'undefined' } get<T>(key: string): T | null { if (this.isServer) return null // ...原有逻辑 } // ...其他方法 }4.2 批量操作与性能优化
对于需要频繁操作的场景,可以实现批量处理:
class BatchStorage extends SmartStorage { private batchQueue = new Map<string, any>() private isProcessing = false batchSet<T>(key: string, value: T, expire?: number) { this.batchQueue.set(key, { value, expire }) if (!this.isProcessing) { this.isProcessing = true requestIdleCallback(() => { this.batchQueue.forEach((data, key) => { this.set(key, data.value, data.expire) }) this.batchQueue.clear() this.isProcessing = false }) } } }4.3 存储空间监控
避免LocalStorage超出5MB限制:
function getRemainingSpace() { const testKey = '__size_test__' try { localStorage.setItem(testKey, new Array(1024 * 1024).join('a')) localStorage.removeItem(testKey) return 5 * 1024 * 1024 - JSON.stringify(localStorage).length } catch (e) { return 0 } }5. 业务场景最佳实践
5.1 用户会话管理
// Vue3示例 const useSession = () => { const { set, get } = useStorage() const SESSION_KEYS = { USER: 'current_user', LAST_ACTIVE: 'last_active' } const startSession = (user: User) => { set(SESSION_KEYS.USER, user, 1800) // 30分钟 set(SESSION_KEYS.LAST_ACTIVE, Date.now(), 1800) } const checkSession = () => { const user = get<User>(SESSION_KEYS.USER) const lastActive = get<number>(SESSION_KEYS.LAST_ACTIVE) if (!user || !lastActive) return false // 超过15分钟未操作视为不活跃 return Date.now() - lastActive < 900000 } }5.2 电商浏览历史
// React示例 const useProductHistory = () => { const [history, setHistory] = useLocalStorage<Product[]>( 'product_history', [], 604800 // 7天过期 ) const addToHistory = (product: Product) => { setHistory([ product, ...history.filter(p => p.id !== product.id).slice(0, 19) ]) } }5.3 多标签页状态同步
通过监听storage事件实现跨标签页通信:
// shared-storage.ts class SharedStorage extends SmartStorage { private listeners = new Map<string, Set<Function>>() constructor() { super() window.addEventListener('storage', this.handleStorageEvent) } private handleStorageEvent = (e: StorageEvent) => { if (e.key && this.listeners.has(e.key)) { const value = e.newValue ? JSON.parse(e.newValue) : null this.listeners.get(e.key)?.forEach(fn => fn(value)) } } subscribe<T>(key: string, callback: (value: T) => void) { if (!this.listeners.has(key)) { this.listeners.set(key, new Set()) } this.listeners.get(key)?.add(callback) return () => { this.listeners.get(key)?.delete(callback) } } }