Aprendizagem do código-fonte Vben-admin (1) - persistência de dados do cliente

1. Pensamentos quebrados

Eu sempre sinto que a escrita ts do meu projeto é muito estranha. Não sei como usá-lo e não sei por onde começar. Não entendo a estrutura do projeto, então comecei a ler o código-fonte de Vben. Eu realmente senti meu couro cabeludo formigando, mas não há como, ainda tenho que dar um passo adiante. Em uma etapa, espero cumpri-lo. Acabei de entender o encapsulamento do armazenamento de dados local e aprendi algumas coisas novas, então registre.

2. Ideia de encapsulamento de armazenamento de dados local Vben

Quando estou trabalhando em um projeto, opero diretamente o localStorage para armazenar dados como tokens. Toda vez que quero obtê-lo, tenho que buscá-lo no localStorage, e cada dado é armazenado em um campo separado. Essa abordagem parece ter funcionado alguns problemas. . No Vben, uma classe de memória é primeiro encapsulada, que é usada para registrar objetos que precisam ser armazenados localmente, e um cronômetro é definido ao atribuir um valor ao objeto de memória, e os atributos no objeto de memória são removidos automaticamente quando ele expira . Ao armazenar em localStorage ou sessionStorage, use JSON.stringfy para serializá-lo em uma string e armazená-lo. Toda vez que você precisar modificar o conteúdo no armazenamento, primeiro modifique o valor da memória correspondente e, em seguida, salve-o como um todo. Obter também é obter o valor da memória. Ou seja, o objeto de memória é manipulado diretamente todas as vezes e, em seguida, o objeto é serializado e armazenado no armazenamento, para que o problema de expiração de valor não precise ser julgado. Aqui está o meu código simplificado

// 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()

3. Alguns pontos de conhecimento que não foram usados ​​antes

(1) Se timeoutId for definido diretamente como número em ts, ele reportará um erro de tipo. Usei window.setTimeout para resolver isso antes. Desta vez aprendi uma nova forma de escrever, mas ainda não sei muito sobre ReturnType

timeoutId: ReturnType<typeof setTimeout>

(2) Reflita

Reflect é uma nova API fornecida pelo ES6 para manipulação de objetos. O objetivo do projeto do objeto Reflect é: colocar alguns métodos do objeto Object que são obviamente internos à linguagem no objeto Reflect; modificar os resultados de retorno de alguns métodos Object para fazer eles se tornem mais razoáveis; deixe as operações de objeto se tornarem comportamentos de função. Mais comumente usados ​​são

// 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) Monitorar alterações de armazenamento

// 当前页面使用的storage被其他页面修改时触发,符合同源策略,在同一浏览器下的不同窗口,
// 当焦点页以外的其他页面导致数据变化时,如果焦点页监听storage事件,那么该事件会触发,
// 换一种说法就是除了改变数据的当前页不会响应外,其他窗口都会响应该事件
window.addEventListener('storage', storageChange)

Guess you like

Origin blog.csdn.net/qq_33235279/article/details/129622199