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库完全没有可比性

posted @ 2023-11-21 11:06  杜柯枫  阅读(119)  评论(0编辑  收藏  举报