前言

在开发过程中很多场景都须要用到前端把数据缓存在端上的能力

  • 业务枚举、标签、配置信息
  • 应用利用期间产生的利用/配置数据
  • 单用户根底信息
  • 依据用户隔离的缓存数据
  • 随业务流动增长的数据缓存
  • 非凡场景的二进制、媒体数据

前端开发者经常会抉择用localStorage来存储须要缓存到前端的数据,然而并不是所有的场景都适宜用这个来治理缓存,滥用还会导致因缓存数据动作失败产生线上问题。

针对不同业务场景,咱们应该抉择不同的计划去缓存数据。本文就针对最常见的存储计划和场景做一些分类和介绍一些在 Vue/React 中的高阶用法,助力前端开发体验和利用的稳定性。同时,缓存键的命名和治理也是值得关注的。

前端缓存计划

除了 cookie,咱们个别会有以下几种抉择:

个性sessionStoragelocalStorageindexedDB
长久时效以后会话期永恒永恒
存储空间5MB5MB依赖可用磁盘空间
同步/异步同步同步异步

针对不同的个性咱们能够得悉存储到sessionStoragelocalStorage中的数据因为存储空间无限都应合乎以下个性:

  1. 缓存数据条目必须可控,不可存储累积性/爆发性增长性质的数据
  2. 单个条目数据不可过大,因而不适宜存储二进制类的数据(例如大文本、图片、音视频等文件)

而对于indexedDB,则更适宜存储大文件和数据条目追随业务操作增长的场景。须要留神的是,存取操作都是异步的。

确定不同场景缓存计划

  • 针对业务枚举、标签类的,这类的信息往往都是字典数据,数据量不大并且更新不频繁,更新前后改变也不大,这类信息是能够存储到localStorage中的
  • 应用利用期间产生的利用/配置数据:这个数据量不大的状况下就能够应用sessionStorage,否则应该思考其余状态治理计划,比方pinia
  • 单用户的根底信息这类信息个别状况是用户在登陆胜利之后后端返回的信息,这类信息条目确定,也适宜存储到localStorage
  • 依据用户隔离的缓存数据:这个如果用localStorage就不合乎咱们说的数据条目必须可控准则,应该存储到indexedDB
  • 随业务流动增长的数据缓存:这个毋庸置疑应该抉择应用indexedDBlocalStorage迟早会爆
  • 非凡场景的二进制、媒体数据:这个也应该抉择应用indexedDB

标准

在一个利用中对于所有缓存的key都应该集中管理,数量多了之后要做分级管理,用枚举来治理,防止随处用随处起名的坏习惯。

咱们能够独自把我的项目应用到的常量独自保护, 从一个进口裸露进来

├── src│   ├── modules│   │   ├── constant│   │   │   └── cache.ts // 缓存相干│   │   │   └── index.ts // 进口

举个子:

// cache.tsexport enum CacheKeyEnum {  USER_INFO = 'user-info',  // ...}// index.tsexport * from './cache'// 应用import { CacheKeyEnum } from '~/modules/constant'function getUserCacheInfo() {  return localStorage.getItem(CacheKeyEnum.USER_INFO)}

这样在我的项目中对立治理key会让我的项目更易于保护。

在我的项目中更繁难、优雅的操作缓存

Vue

得益于@vueuse,咱们能够用useStorage办法把对sessionStoragelocalStorage的操作简化并且和Vue的响应零碎联合:

第一个参数为缓存的key,对应setItem的第一个参数,第二个参数为初始数据,返回值为一个Ref类型的对象,能够间接操作state.value = { hello: "hello", greetinf: "Hi" }响应式更改缓存中的值,上面是一些文档中的例子:

import { useStorage } from '@vueuse/core'// bind objectconst state = useStorage('my-store', { hello: 'hi', greeting: 'Hello' })// bind booleanconst flag = useStorage('my-flag', true) // returns Ref<boolean>// bind numberconst count = useStorage('my-count', 0) // returns Ref<number>// bind string with SessionStorageconst id = useStorage('my-id', 'some-string-id', sessionStorage) // returns Ref<string>// delete data from storagestate.value = null

React

同样的在React中也有相似的库react-use,其中也有useLocalStorage,对应的用法:

import {useLocalStorage} from 'react-use';const Demo = () => {  const [value, setValue] = useLocalStorage('my-key', 'foo');  return (    <div>      <div>Value: {value}</div>      <button onClick={() => setValue('bar')}>bar</button>      <button onClick={() => setValue('baz')}>baz</button>    </div>  );};
useLocalStorage(key);useLocalStorage(key, initialValue);useLocalStorage(key, initialValue, raw);
  • keylocalStorage 键来治理。
  • initialValue — 要设置的初始化值,如果localStorage中的值为空。
  • rawboolean,如果设为 true,钩子将不会尝试 JSON 序列化存储的值。

简化indexedDB的操作

说了sessionStoragelocalStorage的优雅用法,也来说说如何优雅的应用indexedDB

key-value形式的应用

你能够简略了解为localStorage的异步版本、能够存储大体积数据的版本
有这样一个库能够帮咱们简化这个操作:localForage

应用办法也很简略:

import localForage from "localforage";// 你的我的项目IDconst PID = "your project's ID"// 实例let lfInstance: LocalForage/** 初始化LF */export function initLFInstance(): LocalForage {  if (!lfInstance)    lfInstance = localforage.createInstance({ name: PID })  return lfInstance}/** * 设置或读取缓存信息, null: 删除,传参:更新,不传参:获取 * @param key storage key * @param data storage data * @returns storage data | null */export function useIndexedDB<T, K extends string>(key: K, data?: T | null): Promise<T | null> {  if (!lfInstance) {    console.error('lfInstance is not initialized')    return Promise.resolve(null)  }  else {    return data === null      ? (lfInstance!.removeItem(key) as unknown as Promise<null>)      : data === undefined        ? lfInstance!.getItem(key)        : lfInstance!.setItem(key, data)  }}

在利用启动的时候调用一次initLFInstance办法,之后就能够用封装好的useIndexedDB办法来操作indexedDB

SQL级别的存储数据

这种能够间接用原生的写法也能够思考用Dexie.js来操作,会不便很多,比方后面说的切换用户数据的缓存咱们就能够用这个来解决:

构建:

import type { Table } from 'dexie'import Dexie from 'dexie'import { IndexedDBKeys } from '../constant'// 用户数据表类型interface IUserData {  id?: number  name: string  phone: string  age: number}export class AppDataDexie extends Dexie {  userDataDB!: Table<IUserData>  constructor() {    super(IndexedDBKeys.UserData)    this.version(1).stores({      userDataDB: '++id, name, phone, age',    })  }}/** 构建DB实例 */const db = new AppDataDexie()/** 导出用户db操作 */export const userDB = db.userDataDB

应用

import { userDB } from './db'userDB.add({ id: 1, name: 'senar', phone: 'xxxx', age: 22 })// ...更多操作能够查看文档// https://dexie.org/docs/Tutorial/Getting-started

其余一些边界检测

咱们后面说了sessionStoragelocalStorage是有5M容量限度的,咱们如何晓得用户的应用状况呢?

// 检测localStorage应用空间function sieOfLS() {  return Object.entries(localStorage).map(v => v.join('')).join('').length;}// 检测sessionStorage应用空间function sieOfSS() {  return Object.entries(sessionStorage).map(v => v.join('')).join('').length;}

indexedDB也是能够检测容量的,可能在safari浏览器上有兼容性问题,大家也能够参考下:

export async function getCacheUsage() {  const { quota, usage } = await navigator.storage.estimate()  const remainingSpace = quota! - usage!  const unit = 1073741824  const text = `已应用:${(usage ?? 0) / unit} GB, 残余可用缓存空间:${remainingSpace / unit} GB`  console.info(text)  return remainingSpace}

总结

前端缓存的选型须要贴合业务场景来抉择,大家也能够交换分享下本人遇到过的经典场景,看应用哪种计划、如何设计比拟好