Vben-admin源码学习(一)——客户端数据持久化

一、碎碎念

总觉得自己做项目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)

猜你喜欢

转载自blog.csdn.net/qq_33235279/article/details/129622199
今日推荐