推荐使用轻量 Promise 化库 idb 局部封装 IndexedDB,仅封装高频操作(如开库、CRUD、事务),保留游标等底层能力,配合 TypeScript 实现类型安全与错误校验。

用三方库局部封装 IndexedDB,核心思路是选一个轻量、可按需引入的库(如 idb),只封装业务中高频使用的操作(如打开库、增删改查、事务控制),不替代整个 IndexedDB 模型,也不侵入已有逻辑。这样既避开原生 API 的回调嵌套、事件监听、兼容性处理等痛点,又保持对底层行为的可控性。
选 idb:轻量、Promise 化、无副作用
idb(https://github.com/jakearchibald/idb)是目前最推荐的封装层,它不是完整 ORM,而是对 IDBRequest / IDBTransaction 等原生对象做了 Promise 封装,并保留了所有关键能力(如 keyRange、游标遍历、版本升级钩子)。它体积仅 ~3KB(gzip),支持 ESM 按需导入,不污染全局环境。
例如,不用写 onsuccess/onerror 事件,直接:
import { openDB } from 'idb';
<p>const db = await openDB('my-app', 1, {
upgrade(db) {
if (!db.objectStoreNames.contains('users')) {
db.createObjectStore('users', { keyPath: 'id' });
}
}
});</p><p>// 原生需手动调用 add() + 监听 success 事件
await db.transaction('users').objectStore('users').add({ id: 1, name: 'Alice' });
按业务场景封装常用方法,不造轮子
在项目中新建一个 db.ts(或 indexedDB.ts),只封装你真正需要的接口,比如:
- 初始化连接:统一管理 DB 实例和版本升级逻辑
-
CRUD 工具函数:如
getUser(id)、saveUser(user)、deleteUsersByStatus(status) -
批量操作支持:用
transaction().store().putAll()或循环 +Promise.all()控制并发 -
错误映射:把
AbortError、QuotaExceededError转为业务可读错误类型
避免封装“通用 DAO 类”或“Model 映射”,那会快速滑向过度设计。只封装重复出现、易出错、且与业务强相关的逻辑。
保留对事务和游标的显式控制权
局部封装 ≠ 隐藏底层。比如分页加载大量数据时,仍需用游标(cursor)流式读取,而不是一次性 getAll() 导致内存溢出。idb 提供 iterate() 和 openCursor(),可直接使用:
const users: User[] = [];
await db.transaction('users').store('users').iterate((cursor) => {
users.push(cursor.value);
if (users.length >= 100) return false; // 提前终止
});
这样既享受 Promise 写法,又没丢失 IndexedDB 的性能关键能力。封装层里可导出 scanUsers(options) 这类带游标语义的方法,但内部仍是调用原生游标逻辑。
配合 TypeScript 做运行时 + 编译时双校验
利用 idb 的泛型支持,为每个 objectStore 声明类型:
interface User { id: number; name: string; createdAt: Date; }
<p>const db = await openDB<{ users: User }>('my-app', 1, { /<em> ... </em>/ });</p><p>// 类型安全的 get / put
const user = await db.transaction('users').objectStore('users').get(123);
// → user 类型自动推导为 User | undefined
再配合 Zod 或小工具函数做写入前校验(如检查 id 是否缺失),就能在开发阶段捕获大部分数据结构错误,而不依赖运行时 try/catch。


















