IndexedDB 手记
IndexedDB
最新 Chromium V122
版本将一个HTML5特性 WebSQL 被移除了。
虽然在较早的版本就经标记 @deprecated
, 但真的删除,还是有点痛心,毕竟以前用这个技术还做过不少事情。
而官方推荐的替代品IndexedDB看上去又和 WebSQL 有很大的不同。
使用方面,两者差异巨大,WebSQL可以看成客户端版本的SQLite ,使用SQL语句运行交互。而IndexedDB 更像一个 NoSql,不能使用 SQL语句,只能通过API。
虽然都是数据存储,但理念变了,如果产品重度使用了WebSQL的产品可能会有点血崩,将面临整个架构设计的强制切换。
主要优势
相比于 WebSQL, IndexedDB 有一些优势:
- NoSQL 更多灵活,数据直接对象进行存留
- 存储容量 几乎不受限制
- 使用异步操作,WebSQL 为同步
- 安全性更高
- 性能更强,可支持大量结构复杂数据的应用。
- 系统提供一个版本管理系统。
当然 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。
- 轻量:直接引入 https://cdn.jsdelivr.net/npm/idb@8/build/umd.js 时,大小仅1.8kb
- API 与原生indexedDB 完全一致,无学习成本。
- Promise 模型
- 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()
// ...
}
本文完