X.D 笔记X.D 笔记

IndexedDB 手记

IndexedDB 手记

IndexedDB

最新 Chromium V122 版本将一个HTML5特性 WebSQL 被移除了。

虽然在较早的版本就经标记 @deprecated, 但真的删除,还是有点痛心,毕竟以前用这个技术还做过不少事情。

而官方推荐的替代品IndexedDB看上去又和 WebSQL 有很大的不同。

使用方面,两者差异巨大,WebSQL可以看成客户端版本的SQLite ,使用SQL语句运行交互。而IndexedDB 更像一个 NoSql,不能使用 SQL语句,只能通过API。

虽然都是数据存储,但理念变了,如果产品重度使用了WebSQL的产品可能会有点血崩,将面临整个架构设计的强制切换。

主要优势

相比于 WebSQL, IndexedDB 有一些优势:

  1. NoSQL 更多灵活,数据直接对象进行存留
  2. 存储容量 几乎不受限制
  3. 使用异步操作,WebSQL 为同步
  4. 安全性更高
  5. 性能更强,可支持大量结构复杂数据的应用。
  6. 系统提供一个版本管理系统。

当然 NoSQL即是优点也是缺点,特别是对习惯了SQL语句的来说,IndexedDB 有些设计比较麻烦,比如想搜索都可创建索引,分页等。

使用介绍

以下为一个简单的示例,看上去行数有点多,但 IndexedDB 主要的核心方法就几个,熟悉之后就不再有什么难度了。

// 打开或创建名为 myDatabase 的 IndexedDB 数据库
var request = indexedDB.open('myDatabase', 1);

// 数据库版本更新时执行的操作
request.onupgradeneeded = function(event) {
    // 获取数据库实例
    var db = event.target.result;
    // 可以在此处编写版本升级代码,
    // 如果不存在名为 'customers' 的对象存储空间,则创建一个
    if (!db.objectStoreNames.contains('customers')) {
        db.createObjectStore('customers', { keyPath: 'id' });
    }
};

// 打开数据库成功时执行的操作
request.onsuccess = function(event) {
    // 获取数据库实例
    var db = event.target.result;
    
    // 打开一个事务,准备进行数据操作
    var tx = db.transaction(['customers'], 'readwrite');
    
    // 获取名为 'customers' 的对象存储空间
    var objectStore = tx.objectStore('customers');
    
    // 添加一条数据到 'customers' 对象存储空间
    var customer = { id: 1, name: 'XdNote' };
    var requestAdd = objectStore.add(customer);
    
    // 添加数据成功时执行的操作
    requestAdd.onsuccess = function(event) {
        console.log('Data added successfully');
    };
    
    // 在 'customers' 对象存储空间检索数据
    var requestGet = objectStore.get(1);
    
    // 检索数据成功时执行的操作
    requestGet.onsuccess = function(event) {
        // 获取检索到的数据
        var result = event.target.result;
        console.log('Retrieved data:', result);
    };
    
    // 关闭事务
    tx.oncomplete = function(event) {
        db.close(); // 关闭数据库连接
    };
};

// 打开数据库失败时执行的操作
request.onerror = function(event) {
    console.error('Database error:', event.target.error);
};

idb

使用 IndexedDB 的最优体验是 idb

  1. 轻量:直接引入 https://cdn.jsdelivr.net/npm/idb@8/build/umd.js 时,大小仅1.8kb
  2. API 与原生indexedDB 完全一致,无学习成本。
  3. Promise 模型
  4. TypeScript,定义好Schema 的对象,每个属性/索引都有强提示。

上面的代码使用 idb 后,就会清晰明了很多:

import { openDB } from 'idb';
import type { DBSchema, IDBPDatabase } from 'idb';

// 定义数据库架构
interface MyDB extends DBSchema {
  customers: {
    key: number;
    value: { name: string; };
  };
}

// 打开或创建名为 myDatabase 的 IndexedDB 数据库
async function openDatabase() {
  return await openDB<MyDB>('myDatabase', 1, {
    upgrade(db) {
      // 如果不存在名为 'customers' 的对象存储空间,则创建一个
      if (!db.objectStoreNames.contains('customers')) {
        db.createObjectStore('customers', { keyPath: 'key' });
      }
    },
  });
}

// 添加数据到 'customers' 对象存储空间
async function addDataToDatabase() {
  const db = await openDatabase();
  const tx = db.transaction('customers', 'readwrite');
  const store = tx.objectStore('customers');
  const customer = { name: 'XdNote',  };
  await store.add(customer);
  await tx.done;
  console.log('Data added successfully');
  db.close(); // 关闭数据库连接
}

// 从 'customers' 对象存储空间检索数据
async function retrieveDataFromDatabase() {
  const db = await openDatabase();
  const tx = db.transaction('customers', 'readonly');
  const store = tx.objectStore('customers');
  const customer = await store.get(1);
  console.log('Retrieved data:', customer);
  db.close(); // 关闭数据库连接
}

// 添加数据和检索数据的操作
async function main() {
  try {
    await addDataToDatabase();
    await retrieveDataFromDatabase();
  } catch (error) {
    console.error('Database error:', error);
  }
}

// 执行主操作
main();

其它库

IDB是一个极轻的库,没有什么魔法,大部分场合下应该是够用。

如果是重度使用,需要强大的体系支持可以使用 Dexie, 提供了高级查询、链式操作等功能。

出于兼容性考虑,可以使用 localForage, 在不支持 IndexedDB 情况下可降级为 WebSQL.

想获得离线数据同步管理功能,可以使用 PouchDB.

小分享:版本升级

目前基于idb做了一些工作,里面有段管理管理的代码,拿出来分享,可以当做一个简单的工具直接使用

import { openDB } from 'idb';
import type { IDBPDatabase, IDBPObjectStore, DBSchema } from 'idb';

/**
 * 一个版本的定义,为一个方法
*/
export type DBVersion = (db: IDBPDatabase) => Promise<void>;

/**
 * 获取一个数据库连接
*/
export const getDB = async <T extends DBSchema = unknown>(
    dbName: string, versions: DBVersion[]
  ): Promise<IDBPDatabase<T>> => {
    const LAST_VERSION = versions.length;

    const db = await openDB<T>(dbName, LAST_VERSION, {
        upgrade: async (db, oldVersion, newVersion) => {
            console.log(`${dbName} : 检测到新版本,将自动升级,当前版本 ${oldVersion}, 最新版本 : ${newVersion}`)
            const lastVersion = Math.min(newVersion, LAST_VERSION);
            for (let upgradeVersion = oldVersion; upgradeVersion < lastVersion; upgradeVersion++) {
                console.log(`${dbName} : 版本升级 从 ${upgradeVersion} 升级到 ${upgradeVersion + 1}`)
                await versions[upgradeVersion](db as IDBPDatabase);
            }
            console.log(`${dbName} : 已经升级到 ${lastVersion}`)
        }
    });
    return db;
}

使用时,直接引用

import { getDB, type DBVersion } from './util';   // 引入 
import type { IDBPDatabase, DBSchema } from 'idb';

export interface ExtensionSchema extends DBSchema {
    user: {
        key: number
        value: {
          id: number,
          name:string, 
          sex:boolean,
        },
        indexes: {
            // 版本1中没有这个索引,版本2才加上
            sex: string  
        };
    }

}


 const VERSIONS: DBVersion[] = [

    // 版本 1, 创建一个 user 空间
    async (db: IDBPDatabase) => {
        db.createObjectStore('user', { keyPath: 'id', autoIncrement: true });

    },
    // 版本 2, 给 user 空间加入索引 sex
    async (db: IDBPDatabase) => {
        const tx = db.transaction('user','readwrite')
        const user  = tx.objectStore('user')
        user.createIndex('sex','sex')
        // 建立了 sex 索引后,就支持以性别索引数据了。
    },
    // .... 版本 N 每个版本可以使用一个异步方法执行更新操作。
];


// 使用

const exec = async ('DB_NAME',VERSIONS)=>{
  const db = await openDB()
  // ...
}

本文完