当前位置:   article > 正文

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

vben-admin 数据持久化 application

一、碎碎念

总觉得自己做项目ts写得很别扭,很多用法都不会也不知从何学起,对于项目结构也是似懂非懂,于是开始看Vben源码,确实看得头皮发麻,但是没办法,还是得一步一步来,希望能坚持看完,刚理解了本地数据存储的封装,确实有学到一些新东西,记录一下。

二、Vben本地数据存储封装思路

我自己在做项目时,存储token之类的数据都是直接操作localStorage,每次要获取就要从localStorage里面取一次,并且每个数据都单独存一个字段,这种做法好像是有那么点问题。在Vben中首先封装了一个Memory类,该类用于记录需要存储在本地的对象,并且在给memory对象赋值时还会设置一个定时器,到期自动移除memory对象中的属性。在存入localStorage或者sessionStorage时使用JSON.stringfy进行序列化成为一个字符串然后存入。每次需要修改storage中内容时先修改对应memory的值,然后整体存入,获取也是获取memory的值。也就是每次都是直接操作memory对象,然后再将对象序列化存入storage中,这样value过期的问题也不用再判断啦。下面上我简化过一些的代码

  1. // Memory类 memory.ts
  2. /**
  3. * time 到期时间戳
  4. * alive 存活时间 seconds
  5. */
  6. export interface Cache<V = any> {
  7. value?: V
  8. timeoutId?: ReturnType<typeof setTimeout>
  9. time?: number
  10. alive?: number
  11. }
  12. const NOT_ALIVE = 0
  13. export class Memory<T = any, V = any> {
  14. private cache: { [key in keyof T]?: Cache<V> } = {}
  15. private alive: number
  16. constructor(alive = NOT_ALIVE) {
  17. this.alive = alive * 1000
  18. }
  19. get getCache() {
  20. return this.cache
  21. }
  22. setCache(cache: { [key in keyof T]?: Cache<V> }) {
  23. this.cache = cache
  24. }
  25. get(key: keyof T) {
  26. return this.cache[key]
  27. }
  28. // expires传失效日期时间戳
  29. set(key: keyof T, value: V, expires?: number) {
  30. let item = this.get(key)
  31. if (!expires || expires <= 0) {
  32. expires = this.alive
  33. }
  34. if (item) {
  35. if (item.timeoutId) {
  36. clearTimeout(item.timeoutId)
  37. item.timeoutId = undefined
  38. }
  39. item.value = value
  40. item.alive = expires
  41. } else {
  42. item = { value, alive: expires }
  43. this.cache[key] = item
  44. }
  45. const now = new Date().getTime()
  46. item.time = now + item.alive!
  47. item.timeoutId = setTimeout(
  48. () => {
  49. this.remove(key)
  50. },
  51. expires > now ? expires - now : expires
  52. )
  53. }
  54. remove(key: keyof T) {
  55. const item = this.get(key)
  56. Reflect.deleteProperty(this.cache, key)
  57. if (item) {
  58. clearTimeout(item.timeoutId!)
  59. }
  60. }
  61. resetCache(cache: { [key in keyof T]: Cache }) {
  62. Object.keys(cache).forEach((key) => {
  63. const k = key as keyof T
  64. const item = cache[k]
  65. if (item && item.time) {
  66. const now = new Date().getTime()
  67. const expire = item.time
  68. if (expire > now) {
  69. this.set(k, item.value, expire)
  70. }
  71. }
  72. })
  73. }
  74. clear() {
  75. Object.keys(this.cache).forEach((key) => {
  76. const k = key as keyof T
  77. const item = this.cache[k]
  78. item && item.timeoutId && clearTimeout(item.timeoutId)
  79. })
  80. this.cache = {}
  81. }
  82. }
  1. // 封装创建Storage的函数 storageCache.ts
  2. export interface CreateStorageParams {
  3. prefixKey: string
  4. timeout?: Nullable<number>
  5. }
  6. /**
  7. *
  8. * @param timeout Expiration time in seconds
  9. * @param prefixKey 前缀
  10. * @param storage 创建的本地存储类型localStorage或sessionStorage
  11. */
  12. export const createStorage = (
  13. storage = localStorage,
  14. { prefixKey = '', timeout = null }: CreateStorageParams
  15. ) => {
  16. const WebStorage = class WebStorage {
  17. private storage: Storage
  18. private prefixKey: string
  19. constructor() {
  20. this.storage = storage
  21. this.prefixKey = prefixKey
  22. }
  23. private getKey(key: string) {
  24. return `${this.prefixKey}${key}`.toUpperCase()
  25. }
  26. set(key: string, value: any, expire = timeout) {
  27. const stringData = JSON.stringify({
  28. value,
  29. time: Date.now(),
  30. expire: expire ? new Date().getTime() + expire * 1000 : null
  31. })
  32. this.storage.setItem(this.getKey(key), stringData)
  33. }
  34. get(key: string, def: any = null) {
  35. const val = this.storage.getItem(this.getKey(key))
  36. if (!val) return def
  37. try {
  38. const data = JSON.parse(val)
  39. const { value, expire } = data
  40. if (!expire || expire >= new Date().getTime()) {
  41. return value
  42. }
  43. } catch (e) {
  44. return def
  45. }
  46. }
  47. remove(key: string) {
  48. this.storage.removeItem(this.getKey(key))
  49. }
  50. clear() {
  51. this.storage.clear()
  52. }
  53. }
  54. return new WebStorage()
  55. }
  56. // 导出分别创建localStorage和sessionStorage的函数
  57. export const createLocalStorage = (
  58. options: CreateStorageParams = { prefixKey: '', timeout: null }
  59. ) => {
  60. return createStorage(localStorage, options)
  61. }
  62. export const createSessionStorage = (
  63. options: CreateStorageParams = { prefixKey: '', timeout: null }
  64. ) => {
  65. return createStorage(sessionStorage, options)
  66. }
  1. // 存储数据实操类 persistent.ts
  2. import { Memory } from './memory'
  3. import { DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting'
  4. import {
  5. ROLES_KEY,
  6. TOKEN_KEY,
  7. USER_INFO_KEY,
  8. APP_LOCAL_CACHE_KEY,
  9. APP_SESSION_CACHE_KEY
  10. } from '@/enums/cacheEnum'
  11. import { UserInfo } from '@/types/store'
  12. import { toRaw } from 'vue'
  13. import { createLocalStorage, createSessionStorage } from './storageCache'
  14. interface BasicStore {
  15. [TOKEN_KEY]: string | number | null | undefined
  16. [USER_INFO_KEY]: UserInfo
  17. [ROLES_KEY]: string[]
  18. }
  19. type LocalStore = BasicStore
  20. type SessionStore = BasicStore
  21. type LocalKeys = keyof LocalStore
  22. type SessionKeys = keyof SessionStore
  23. const localMemory = new Memory(DEFAULT_CACHE_TIME)
  24. const sessionMemory = new Memory(DEFAULT_CACHE_TIME)
  25. const ls = createLocalStorage()
  26. const ss = createSessionStorage()
  27. function initPersistentMemory() {
  28. const localCache = ls.get(APP_LOCAL_CACHE_KEY)
  29. const sessionStorage = ss.get(APP_SESSION_CACHE_KEY)
  30. localCache && localMemory.resetCache(localCache)
  31. sessionStorage && sessionMemory.resetCache(sessionStorage)
  32. }
  33. export class Persistent {
  34. static getLocal(key: LocalKeys) {
  35. return localMemory.get(key)?.value
  36. }
  37. static setLocal(
  38. key: LocalKeys,
  39. value: LocalStore[LocalKeys],
  40. immadiate = false
  41. ) {
  42. localMemory.set(key, toRaw(value))
  43. // TODO
  44. immadiate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache)
  45. }
  46. static removeLocal(key: LocalKeys, immadiate = false) {
  47. localMemory.remove(key)
  48. immadiate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache)
  49. }
  50. static clearLocal(immadiate = false) {
  51. localMemory.clear()
  52. immadiate && ls.clear()
  53. }
  54. static getSession(key: SessionKeys) {
  55. return sessionMemory.get(key)?.value
  56. }
  57. static setSession(
  58. key: SessionKeys,
  59. value: SessionStore[SessionKeys],
  60. immediate = false
  61. ) {
  62. sessionMemory.set(key, toRaw(value))
  63. immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache)
  64. }
  65. static removeSession(key: SessionKeys, immediate = false): void {
  66. sessionMemory.remove(key)
  67. immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache)
  68. }
  69. static clearSession(immediate = false): void {
  70. sessionMemory.clear()
  71. immediate && ss.clear()
  72. }
  73. static clearAll(immediate = false) {
  74. localMemory.clear()
  75. sessionMemory.clear()
  76. if (immediate) {
  77. ls.clear()
  78. ss.clear()
  79. }
  80. }
  81. }
  82. function storageChange(e: any) {
  83. const { key, newValue, oldValue } = e
  84. if (!key) {
  85. Persistent.clearAll()
  86. return
  87. }
  88. if (!!newValue && !!oldValue) {
  89. if (APP_LOCAL_CACHE_KEY === key) {
  90. Persistent.clearLocal()
  91. }
  92. if (APP_SESSION_CACHE_KEY === key) {
  93. Persistent.clearSession()
  94. }
  95. }
  96. }
  97. // 当前页面使用的storage被其他页面修改时触发,符合同源策略,在同一浏览器下的不同窗口,
  98. // 当焦点页以外的其他页面导致数据变化时,如果焦点页监听storage事件,那么该事件会触发,
  99. // 换一种说法就是除了改变数据的当前页不会响应外,其他窗口都会响应该事件
  100. window.addEventListener('storage', storageChange)
  101. initPersistentMemory()

三、一些以前没用过的知识点

(1) 对于timeoutId在ts中如果直接定义为number会报类型错误,我之前都是用window.setTimeout来解决,这次学到了新的写法,但是对于ReturnType还是一知半解

timeoutId: ReturnType<typeof setTimeout>

(2) Reflect

Reflect是ES6为了操作对象而提供的新的API,Reflect对象的设计目的为:将Object对象的一些明显属于语言内部的方法放到Reflect对象上;修改某些Object方法的返回结果,让其变得更合理;让Object操作都变成函数行为。比较常用的有

  1. // Object.defineProperty无法定义时抛出错误,而Reflect.defineProperty返回false
  2. Reflect.defineProperty(target, propertyKey, attributes)
  3. // delete obj[name]
  4. // Reflect.deleteProperty返回boolean,操作成功或者属性不存在返回true
  5. Reflect.deleteProperty(obj, name)
  6. // name in obj
  7. Reflect.has(obj,name)

(3) 监听storage变化

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/165508?site
推荐阅读
相关标签
  

闽ICP备14008679号