🔍 场景分析 为什么要把大文件存在前端?在某些应用场景下,对于需要频繁加载文件的web应用。 如在线编辑器,文件预览, 文件解析。 这样做。能够给服务器剩下很大一笔带宽。这么做是很有必要的。
使用需求场景代码如下: 前端根据记录的id,获取加载详情需要的本地文件。如果没有我封装的DB模块就会报错,就会走到下面的加载文件的后端接口,并把文件给保存到indexDB里,这样下次就不用走后端了。
// 从本地读取文件
try {
const dbReadFile = await Db.getValue(`pdfFile:${item.requestId}`)
if (dbReadFile) {
setCurPdf(URL.createObjectURL(dbReadFile));
}
} catch (err) {
const file = await getFile(item.requestId);
setCurPdf(URL.createObjectURL(file));
await Db.setValue(`pdfFile:${item.requestId}`, file)
await saveFileTimeDb.setValue(item.requestId.toString(), new Date().getTime())
}
🔄 性能考量 前端存储可以减少服务器的负担,加快数据的读写速度,提升用户体验。
🛠️ 为什么选择IndexDB
- LocalStorage:简单易用,但容量有限。
- IndexedDB:功能强大,适合存储大量结构化数据。
- Web SQL(已废弃)和FileSystem API(实验性)。
🔍 IndexedDB是什么? IndexedDB是一个运行在浏览器中的非关系型数据库,它允许网页应用存储大量数据。与传统的cookie和localStorage相比,IndexedDB提供了更复杂的数据存储能力。
🛠️ 主要特点
- 大量数据存储:可以存储比localStorage更多的数据。
- 异步操作:不阻塞主线程,提升网页性能。
- 事务支持:保证数据操作的原子性。
- 键值对存储:通过键来索引数据,提高数据检索效率。
🔄 使用场景
- 存储大量数据,如用户数据、缓存的网页内容等。
- 需要事务支持的场景,确保数据的一致性。
📚 基本操作
- 打开数据库:使用
open
方法打开或创建数据库。 - 创建对象存储:在数据库中创建存储数据的对象存储。
- 读取和写入数据:通过事务进行数据的读取和写入。
- 索引:创建索引以优化查询性能。
🌟 为什么选择IDB? 在前端开发中,本地存储是一个常见需求。IDB这个包,提供了比原生IndexDB更简单, 更方便的方法,你不用再担心数据库版本同步问题。它会帮你解决
🛠️ 如何封装IDB? 我将IDB封装成了一个易于使用的CrxIndexDB
类,它提供了getValue
、setValue
和deleteValue
三个公共方法,让本地数据操作变得简单快捷。
封装代码代码如下:
import { IDBPDatabase, openDB } from 'idb'
/**
* 封装indexDB方便background进行本地缓存
* 暴露三个公共方法(异步调用):
* getValue
* setValue
* deleteValue
*
* 同时注册这三个方法的Message消息,便于contentScript调用
*/
class CrxIndexDB {
private database: string
private tableName: string
private db: any
constructor(database: string, tableName: string) {
this.database = database
this.tableName = tableName
this.createObjectStore()
}
public async getAllData() {
// 打开数据库
const dbName = this.database;
const storeName = this.tableName;
const db = await openDB(dbName, 1, {
upgrade(db) {
// 这里假设对象存储已经存在,如果不存在可以在这里创建
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName);
}
}
});
// 获取对象存储中的所有数据
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const allData = await store.getAll();
await tx.done;
return allData;
}
public async getValue(keyName: string): Promise<any> {
await this.dbReady()
const { tableName } = this
const tx = this.db.transaction(tableName, 'readonly')
const store = tx.objectStore(tableName)
const result = await store.get(keyName)
return result.value
}
public async setValue(keyName: string, value: any) {
await this.dbReady()
const { tableName } = this
const tx = this.db.transaction(tableName, 'readwrite')
const store = tx.objectStore(tableName)
const result = await store.put({
keyName,
value
})
return result
}
public async deleteValue(keyName: string) {
await this.dbReady()
const { tableName } = this
const tx = this.db.transaction(tableName, 'readwrite')
const store = tx.objectStore(tableName)
const result = await store.get(keyName)
if (!result) {
return result
}
await store.delete(keyName)
return keyName
}
private sleep = (num): Promise<boolean> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, num * 1000)
})
}
private async dbReady() {
if (!this.db) {
await this.sleep(0.5)
return await this.dbReady()
}
return true
}
private async createObjectStore() {
const tableName = this.tableName
try {
this.db = await openDB(this.database, 1, {
upgrade(db: IDBPDatabase) {
if (db.objectStoreNames.contains(tableName)) {
return
}
db.createObjectStore(tableName, {
keyPath: 'keyName'
})
}
})
} catch (error) {
return false
}
}
}