一、碎碎念
总觉得自己做项目ts写得很别扭,很多用法都不会也不知从何学起,对于项目结构也是似懂非懂,于是开始看Vben源码,确实看得头皮发麻,但是没办法,还是得一步一步来,希望能坚持看完,刚理解了本地数据存储的封装,确实有学到一些新东西,记录一下。
二、Vben本地数据存储封装思路
我自己在做项目时,存储token之类的数据都是直接操作localStorage,每次要获取就要从localStorage里面取一次,并且每个数据都单独存一个字段,这种做法好像是有那么点问题。在Vben中首先封装了一个Memory类,该类用于记录需要存储在本地的对象,并且在给memory对象赋值时还会设置一个定时器,到期自动移除memory对象中的属性。在存入localStorage或者sessionStorage时使用JSON.stringfy进行序列化成为一个字符串然后存入。每次需要修改storage中内容时先修改对应memory的值,然后整体存入,获取也是获取memory的值。也就是每次都是直接操作memory对象,然后再将对象序列化存入storage中,这样value过期的问题也不用再判断啦。下面上我简化过一些的代码
// Memory类 memory.ts /** * time 到期时间戳 * alive 存活时间 seconds */ export interface Cache<V = any> { value?: V timeoutId?: ReturnType<typeof setTimeout> time?: number alive?: number } const NOT_ALIVE = 0 export class Memory<T = any, V = any> { private cache: { [key in keyof T]?: Cache<V> } = {} private alive: number constructor(alive = NOT_ALIVE) { this.alive = alive * 1000 } get getCache() { return this.cache } setCache(cache: { [key in keyof T]?: Cache<V> }) { this.cache = cache } get(key: keyof T) { return this.cache[key] } // expires传失效日期时间戳 set(key: keyof T, value: V, expires?: number) { let item = this.get(key) if (!expires || expires <= 0) { expires = this.alive } if (item) { if (item.timeoutId) { clearTimeout(item.timeoutId) item.timeoutId = undefined } item.value = value item.alive = expires } else { item = { value, alive: expires } this.cache[key] = item } const now = new Date().getTime() item.time = now + item.alive! item.timeoutId = setTimeout( () => { this.remove(key) }, expires > now ? expires - now : expires ) } remove(key: keyof T) { const item = this.get(key) Reflect.deleteProperty(this.cache, key) if (item) { clearTimeout(item.timeoutId!) } } resetCache(cache: { [key in keyof T]: Cache }) { Object.keys(cache).forEach((key) => { const k = key as keyof T const item = cache[k] if (item && item.time) { const now = new Date().getTime() const expire = item.time if (expire > now) { this.set(k, item.value, expire) } } }) } clear() { Object.keys(this.cache).forEach((key) => { const k = key as keyof T const item = this.cache[k] item && item.timeoutId && clearTimeout(item.timeoutId) }) this.cache = {} } }
复制
// 封装创建Storage的函数 storageCache.ts export interface CreateStorageParams { prefixKey: string timeout?: Nullable<number> } /** * * @param timeout Expiration time in seconds * @param prefixKey 前缀 * @param storage 创建的本地存储类型localStorage或sessionStorage */ export const createStorage = ( storage = localStorage, { prefixKey = '', timeout = null }: CreateStorageParams ) => { const WebStorage = class WebStorage { private storage: Storage private prefixKey: string constructor() { this.storage = storage this.prefixKey = prefixKey } private getKey(key: string) { return `${this.prefixKey}${key}`.toUpperCase() } set(key: string, value: any, expire = timeout) { const stringData = JSON.stringify({ value, time: Date.now(), expire: expire ? new Date().getTime() + expire * 1000 : null }) this.storage.setItem(this.getKey(key), stringData) } get(key: string, def: any = null) { const val = this.storage.getItem(this.getKey(key)) if (!val) return def try { const data = JSON.parse(val) const { value, expire } = data if (!expire || expire >= new Date().getTime()) { return value } } catch (e) { return def } } remove(key: string) { this.storage.removeItem(this.getKey(key)) } clear() { this.storage.clear() } } return new WebStorage() } // 导出分别创建localStorage和sessionStorage的函数 export const createLocalStorage = ( options: CreateStorageParams = { prefixKey: '', timeout: null } ) => { return createStorage(localStorage, options) } export const createSessionStorage = ( options: CreateStorageParams = { prefixKey: '', timeout: null } ) => { return createStorage(sessionStorage, options) }
复制
// 存储数据实操类 persistent.ts import { Memory } from './memory' import { DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting' import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY, APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY } from '@/enums/cacheEnum' import { UserInfo } from '@/types/store' import { toRaw } from 'vue' import { createLocalStorage, createSessionStorage } from './storageCache' interface BasicStore { [TOKEN_KEY]: string | number | null | undefined [USER_INFO_KEY]: UserInfo [ROLES_KEY]: string[] } type LocalStore = BasicStore type SessionStore = BasicStore type LocalKeys = keyof LocalStore type SessionKeys = keyof SessionStore const localMemory = new Memory(DEFAULT_CACHE_TIME) const sessionMemory = new Memory(DEFAULT_CACHE_TIME) const ls = createLocalStorage() const ss = createSessionStorage() function initPersistentMemory() { const localCache = ls.get(APP_LOCAL_CACHE_KEY) const sessionStorage = ss.get(APP_SESSION_CACHE_KEY) localCache && localMemory.resetCache(localCache) sessionStorage && sessionMemory.resetCache(sessionStorage) } export class Persistent { static getLocal(key: LocalKeys) { return localMemory.get(key)?.value } static setLocal( key: LocalKeys, value: LocalStore[LocalKeys], immadiate = false ) { localMemory.set(key, toRaw(value)) // TODO immadiate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache) } static removeLocal(key: LocalKeys, immadiate = false) { localMemory.remove(key) immadiate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache) } static clearLocal(immadiate = false) { localMemory.clear() immadiate && ls.clear() } static getSession(key: SessionKeys) { return sessionMemory.get(key)?.value } static setSession( key: SessionKeys, value: SessionStore[SessionKeys], immediate = false ) { sessionMemory.set(key, toRaw(value)) immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache) } static removeSession(key: SessionKeys, immediate = false): void { sessionMemory.remove(key) immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache) } static clearSession(immediate = false): void { sessionMemory.clear() immediate && ss.clear() } static clearAll(immediate = false) { localMemory.clear() sessionMemory.clear() if (immediate) { ls.clear() ss.clear() } } } function storageChange(e: any) { const { key, newValue, oldValue } = e if (!key) { Persistent.clearAll() return } if (!!newValue && !!oldValue) { if (APP_LOCAL_CACHE_KEY === key) { Persistent.clearLocal() } if (APP_SESSION_CACHE_KEY === key) { Persistent.clearSession() } } } // 当前页面使用的storage被其他页面修改时触发,符合同源策略,在同一浏览器下的不同窗口, // 当焦点页以外的其他页面导致数据变化时,如果焦点页监听storage事件,那么该事件会触发, // 换一种说法就是除了改变数据的当前页不会响应外,其他窗口都会响应该事件 window.addEventListener('storage', storageChange) initPersistentMemory()
复制
三、一些以前没用过的知识点
(1) 对于timeoutId在ts中如果直接定义为number会报类型错误,我之前都是用window.setTimeout来解决,这次学到了新的写法,但是对于ReturnType还是一知半解
timeoutId: ReturnType<typeof setTimeout>
复制
(2) Reflect
Reflect是ES6为了操作对象而提供的新的API,Reflect对象的设计目的为:将Object对象的一些明显属于语言内部的方法放到Reflect对象上;修改某些Object方法的返回结果,让其变得更合理;让Object操作都变成函数行为。比较常用的有
// Object.defineProperty无法定义时抛出错误,而Reflect.defineProperty返回false Reflect.defineProperty(target, propertyKey, attributes) // delete obj[name] // Reflect.deleteProperty返回boolean,操作成功或者属性不存在返回true Reflect.deleteProperty(obj, name) // name in obj Reflect.has(obj,name)
复制
(3) 监听storage变化
// 当前页面使用的storage被其他页面修改时触发,符合同源策略,在同一浏览器下的不同窗口, // 当焦点页以外的其他页面导致数据变化时,如果焦点页监听storage事件,那么该事件会触发, // 换一种说法就是除了改变数据的当前页不会响应外,其他窗口都会响应该事件 window.addEventListener('storage', storageChange)
复制