IndexedDB封装
重点
数据库初始创建或更新后会先触发onupgradeneeded方法,然后再触发onsuccess方法,如果在onupgradeneeded方法中执行了表结构操作的话,onsuccess会在transaction.oncomplete事件处理之后触发,所以在onsuccess方法中去执行增删改一定是在表完整结构基础上不必担心表错误
代码
export default class IndexedDB {
/** 表名 */
private tableName: string;
/** 键列表,第一个为主键,其余为索引键 */
private keyList: string[];
/** 等待连接数据库的 Promise */
awaitOpenDB: Promise<IDBDatabase>;
// 构造方法
constructor(
tableName: 'indexedTable',
keyList: string[],
dbName: 'indexedDB'
) {
this.tableName = tableName;
this.keyList = keyList;
this.awaitOpenDB = this.openDB(dbName);
}
/** 连接数据库并返回 Promise */
private openDB(dbName: string): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = window.indexedDB.open(dbName);
// 创建或更新数据库时调用
request.onupgradeneeded = (e) =>
this.initDB((e.target as IDBOpenDBRequest).result);
// 连接成功
request.onsuccess = (e) => resolve((e.target as IDBOpenDBRequest).result);
// 连接失败
request.onerror = (e) => reject((e.target as IDBOpenDBRequest).error);
});
}
/** 初始化数据库 */
private initDB(db: IDBDatabase) {
// 拆出主键和索引键
const [keyPath, ...otherKey] = this.keyList;
// 创建创建对象存储(表)和主键
const objStore = db.createObjectStore(this.tableName, {
keyPath: keyPath || 'id',
autoIncrement: true,
});
// 创建索引
otherKey.forEach((key) => {
if (!objStore.indexNames.contains(key)) {
objStore.createIndex(key, key, { unique: false });
}
});
}
/** 设置一条数据 */
private async setItem(obj: Record<string, any>): Promise<void> {
const db = await this.awaitOpenDB;
return new Promise((resolve, reject) => {
const request = db
.transaction([this.tableName], 'readwrite')
.objectStore(this.tableName)
.put(obj); // 插入或更新数据
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error('设置数据失败'));
});
}
/** 设置数据 */
public async setData(list: Array<Record<string, any>> | Record<string, any>) {
const arr = Array.isArray(list) ? list : [list]; // 统一处理为数组
return Promise.allSettled(arr.map((v) => this.setItem(v)));
}
/** 获取数据 */
public async getData(
obj: Record<string, any>
): Promise<Array<Record<string, any>>> {
const db = await this.awaitOpenDB;
const objectStore = db
.transaction(this.tableName)
.objectStore(this.tableName);
const objKeyList = Object.keys(obj); // 获取查询条件的键
const results: Array<Record<string, any>> = [];
return new Promise((resolve) => {
objectStore.openCursor().onsuccess = (event) => {
const cursor = (event.target as IDBRequest).result;
if (cursor) {
if (objKeyList.every((v) => obj[v] === cursor.value[v])) {
results.push(cursor.value); // 匹配则添加到结果中
}
cursor.continue(); // 继续遍历
} else {
resolve(results); // 遍历结束,返回结果
}
};
});
}
/** 通过主键删除数据 */
public async removeItem(keyPath: string): Promise<void> {
const db = await this.awaitOpenDB;
return new Promise((resolve, reject) => {
const request = db
.transaction([this.tableName], 'readwrite')
.objectStore(this.tableName)
.delete(keyPath); // 根据主键删除数据
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error('删除数据失败'));
});
}
/** 删除符合条件的数据 */
public async removeData(obj: Record<string, any>) {
// 由于只能通过主键删除数据,所以要先根据条件进行查询,后逐条进行删除操作
const res = await this.getData(obj);
return Promise.allSettled(
res.map((v) => this.removeItem(v[this.keyList[0]]))
);
}
}
使用
storageDB = new indexedDB('表名',['id','name'],'库名')
// indexedDBClass中参数都非必填
// 默认表名indexedTable
// 默认表名字段列表['id']
// 默认库名indexedDB
// 使用数据库的代码都要在这里面运行,不然会出现数据库未链接就使用的情况
// 获取数据(参数是匹配条件)
let list = await storageDB.getData({ name: '张三' })
// 新增&插入数据(参数是完整的要存储的数据)
await storageDB.setData({ name: '李四' })
// 删除数据(参数是匹配条件)
await storageDB.removeData({ name: '张三' })
})
评价
indexedDb有点类似于storage的存储方式,特别是在存取单条数据的时候几乎写法大差不差
对于批量操作没有任何可用的api,只能化整为零逐条操作
查询功能更是一塌糊涂,仅支持单个键全匹配或者获取全部,是不是跟storage获取超级相似,跟sql库完全没有可比性