vue3项目使用IndexDB经验分享

前言

IndexDB是一个相对底层一点API。通常用于存储大量结构化数据,包括文件、Blob等。IndexDB的API使用索引来实现对数据的高性能读取。

localStorage、sessionStorage是浏览器最常用的数据存储方案。两个各有优缺点。

  • localStorage支持持久性存储,浏览器会话结束依旧可以存储数据在浏览器内存中,方便下次数据读取与使用。但缺点是存储的大小有限制,不同的浏览器基于的存储空间有所区别,但是都在5M左右。对于普通的业务场景足够胜任。但是对于通讯应用,存储对话记录这种高密度数据存储的场景来说,内存空间完全是不够用的。
  • sessionStorage支持会话存储,浏览器相同域名会话窗口之间支持内存数据共享。当会话结束之后,关闭会话窗口,内存数据会被清除。缺点很明显,会话不支持永久存储,同时内存空间也很小。

三者都是基于源(Origin)的对象存储。源(Origin)指的是协议、域名、端口。不同的源数据是不支持共享的,相互之间保持独立,能够完美的避免不同源的数据污染。

www.baidu.comwww.google.com两个是不同的源,数据均不支持共享。

如果不同源数据支持共享会造成严重的数据污染,如下面的场景:

www.baidu.com源存储了一个数据 键为a,值为b

www.google.com源存储了一个数据 键为a,值为b

两键值对修改会造成相互相应,从而引发数据污染。

IndexDB优势

两者最显著的缺点是

  1. 存储空间太小
  2. 键值对总是以字符串形式存储,不支持其他数据格式,如Blob、ArrayBuffer
  3. 不支持异步操作,数据量大时进行I/O操作性能体验差

IndexDb成为了如上问题的解决方案。

空间大小

IndexDB存储空间根据不同的设备而定,因为不同设备存储空间大小不一致,浏览器的最大存储空间是动态的,它完全取决于你的磁盘大小。全局限制为可用磁盘空间的 50%。在 Firefox 中,一个名为 Quota Manager 的内部浏览器工具会跟踪每个源用尽的磁盘空间,并在必要时删除数据。

因此,如果你的硬盘驱动器是 500GB,那么浏览器的总存储容量为 250GB。如果超过此范围,则会发起称为源回收的过程,删除整个源的数据,直到存储量再次低于限制。删除源数据没有只删一部分的说法------因为这样可能会导致不一致的问题。

另外一个是当存储空间达到上限之后会执行LRU策略,当可用磁盘空间已满时,配额管理器将根据 LRU 策略开始清除数据------最近最少使用的源将首先被删除,然后是下一个,直到浏览器不再超过限制。

异步执行

IndexDB执行的操作都是异步完成的,以免阻塞应用程序。

由于存储不涉及到浏览器的Document操作,因此我们还可以配合Web Worker实现数据交互。把IndexDB的操作都放在Web Worker中,大大提升性能。

使用方法

下面来介绍一下如何在Vue项目中使用IndexDB,我们实现两套方案,第一种是基于Hook模式,第二种是基于Class模式。

这样能够很好的应用到Vue2/3中(React同理)。

基于Hook

使用数据库首先需要进行数据库注册,注册成功之后,才可以进行数据的事务操作。

typescript 复制代码
import { Toast } from "vant";

interface Result {
  name: string;
  content: string;
}

interface Get {
  result: Result;
  onsuccess: () => void;
  onerror: (reason?: any) => void;
}

interface Put {
  onsuccess: (target: { result: Result }) => void;
  onerror: (reason?: any) => void;
}

const SQL_NAME = "yuyishijia-h5";
const DB_NAME = "h5";
let request: any;
let db: any;

// 初始化数据库
function init() {
  return new Promise((resolve, reject) => {
    request = window.indexedDB.open(SQL_NAME);
    request.onerror = (event: any) => {
      reject(event);
      Toast("缓存获取失败");
    };
    request.onsuccess = (event: any) => {
      resolve(event.target.result);
      db = event.target.result;
    };
    request.onupgradeneeded = (event: any) => {
      db = event.target.result;
      let objectStore;
      if (!db.objectStoreNames.contains(DB_NAME)) {
        // 创建数据库
        objectStore = db.createObjectStore(DB_NAME, {
          keyPath: "name",
          unique: true, // 使用name名称作为主键,且不允许重复
        });
        objectStore.createIndex("content", "content", { unique: false }); // 建立索引
      }
    };
  });
}

// get操作,读取数据
export async function get(name: string): Promise<Result> {
  return new Promise<Result>((resolve, reject) => {
  const select: Get = db
    .transaction([DB_NAME], "readonly")
    .objectStore(DB_NAME)
    .get(name);

  select.onsuccess = function () {
    resolve(select.result);
  };
  select.onerror = reject;
});
}

// add操作,添加数据
export async function add(name: string, content: string): Promise<Result> {
  return new Promise<Result>((resolve, reject) => {
  const select: Put = db
    .transaction([DB_NAME], "readwrite")
    .objectStore(DB_NAME)
    .add({ name: name, content });

  select.onsuccess = (event: any) => {
    resolve(event.target.result);
  };
  select.onerror = reject;
});
}

// update操作,更新数据
export async function update(name: string, content: string): Promise<Result> {
  return new Promise((resolve, reject) => {
  const select: Put = db
    .transaction([DB_NAME], "readwrite")
    .objectStore(DB_NAME)
    .put({ name, content });

  select.onsuccess = (event: any) => {
    resolve(event.target.result);
  };
  select.onerror = reject;
});
}

// remove操作,删除数据
export async function remove(name: string): Promise<Result> {
  return new Promise((resolve, reject) => {
  const select: Put = db
    .transaction([DB_NAME], "readwrite")
    .objectStore(DB_NAME)
    .delete(name);

  select.onsuccess = (event: any) => {
    resolve(event.target.result);
  };
  select.onerror = reject;
});
}

export default {
  init,
  add,
  get,
  update,
  remove,
};

在使用IndexDB之前需要先在入口函数注册数据库。

typescript 复制代码
import { createApp } from "vue";
import indexDB from "@/utils/storage";

await indexDB.init(); // 在入口函数初始化
const app = createApp(App);
app.mount("#app");

注册成功后,可以在业务中对数据库表进行增删改查操作

typescript 复制代码
<template>
  <div>使用IndexDB</div>
</template>

<script setup>
import IndexDB from "@/utils/storage";

// 加载数据
const loadData = async () => {
  let cache: any;
  try {
    cache = await IndexDB.get('主键名称');
  } catch (error) {
    console.log(error);
  }
};
</script>

基于Class

typescript 复制代码
import { Toast } from "vant";

const SQL_NAME = "yuyishijia-h5";
const DB_NAME = "h5";

class indexDb {
  name: string; // 主键
  request: any;
  db: any;
  constructor(name: string) {
    this.name = name;
  }

  init() {
    return new Promise((resolve, reject) => {
      this.request = window.indexedDB.open(SQL_NAME);
      this.request.onerror = (event: any) => {
        reject(event);
        Toast("缓存获取失败");
      };
      this.request.onsuccess = (event: any) => {
        resolve(event.target.result);
        this.db = event.target.result;
      };
      this.request.onupgradeneeded = (event: any) => {
        this.db = event.target.result;
        let objectStore;
        if (!this.db.objectStoreNames.contains(DB_NAME)) {
          objectStore = this.db.createObjectStore(DB_NAME, {
            keyPath: "name",
            unique: true, // 名称作为主键不允许重复
          });
          objectStore.createIndex("content", "content", { unique: false });
        }
      };
    });
  }

  async get(name: string) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([DB_NAME], "readonly");
      const objectStore = transaction.objectStore(DB_NAME);
      const req = objectStore.get(name);

      req.onsuccess = function () {
        resolve(req.result);
      };
      req.onerror = reject;
    });
  }

  async add(content: string) {
    return new Promise((resolve, reject) => {
      const select = this.db
        .transaction([DB_NAME], "readwrite")
        .objectStore(DB_NAME)
        .add({ name: this.name, content });

      select.onsuccess = (event: any) => {
        resolve(event.target.result);
      };
      select.onerror = reject;
    });
  }

  async update(name: string, content: string) {
    return new Promise((resolve, reject) => {
      const select = this.db
        .transaction([DB_NAME], "readwrite")
        .objectStore(DB_NAME)
        .put({ name, content });

      select.onsuccess = (event: any) => {
        resolve(event.target.result);
      };
      select.onerror = reject;
    });
  }
}

export default indexDb;

class模式推荐配合pinia(vuex)做全局数据存储。

typescript 复制代码
import { defineStore } from 'pinia'
import indexDB from "@/utils/storage";

export const useDbStore = defineStore('db', () => {
    const db = new indexDB()
    db.init() // 初始化
    
    
    return { add: db.add, update: db.update, remove: db.remove, get: db.get } 
 })
相关推荐
Danta38 分钟前
百度网盘一面值得look:我有点难受🤧🤧
前端·javascript·面试
OpenTiny社区1 小时前
TinyVue v3.22.0 正式发布:深色模式上线!集成 UnoCSS 图标库!TypeScript 类型支持全面升级!
前端·vue.js·开源
海上彼尚2 小时前
使用Autocannon.js进行HTTP压测
开发语言·javascript·http
工业互联网专业2 小时前
基于JavaWeb的花店销售系统设计与实现
java·vue.js·spring boot·毕业设计·源码·课程设计·花店销售系统
layman05283 小时前
node.js 实战——(fs模块 知识点学习)
javascript·node.js
毕小宝3 小时前
编写一个网页版的音频播放器,AI 加持,So easy!
前端·javascript
万水千山走遍TML3 小时前
JavaScript性能优化
开发语言·前端·javascript·性能优化·js·js性能
Aphasia3113 小时前
react必备JS知识点(一)——判断this指向👆🏻
前端·javascript·react.js
会飞的鱼先生3 小时前
vue3中slot(插槽)的详细使用
前端·javascript·vue.js
知心宝贝3 小时前
🔍 从简单到复杂:JavaScript 事件处理的全方位解读
前端·javascript·面试