IndexedDB/React问题:创建数据库后无法新建对象仓库或执行CRUD
IndexedDB 在React应用中创建对象仓库时事件不触发的问题
我正在构建一个React测试应用来深入理解IndexedDB API,但在创建对象仓库时遇到了问题:数据库只有在页面刷新且已存在的情况下才能更新,否则onsuccess和onupgradeneeded事件完全不触发。
代码实现
iddb.ts
let request: IDBOpenDBRequest; let db: IDBDatabase; let version = 1; // 初始化数据库,创建默认仓库 export const initDB = () => { console.log('initDB'); request = window.indexedDB.open(DBName.Main); // 不指定版本,使用现有版本 /* onupgradeneeded 在数据库创建或版本变更时触发 只有在添加、删除或修改仓库时才应该变更版本 */ request.onupgradeneeded = (_event) => { db = request.result; if (!db.objectStoreNames.contains(StoreName.Users)) { console.log(`Creating ${StoreName.Users} store`); db.createObjectStore(StoreName.Users, { keyPath: 'id' }); } if (!db.objectStoreNames.contains(StoreName.Posts)) { console.log(`Creating ${StoreName.Posts} store`); db.createObjectStore(StoreName.Posts, { keyPath: 'id' }); } }; request.onsuccess = (_event) => { console.log('request.onsuccess - initDB'); version = request.result.version; }; }; export const deleteDB = () => { request = window.indexedDB.deleteDatabase(DBName.Main); request.onsuccess = (_event) => { console.log('Database deleted successfully'); }; }; export const createStore = (storeName: StoreName) => { version += 1; request = window.indexedDB.open(DBName.Main, version); // 以新版本打开数据库 request.onupgradeneeded = (_event) => { db = request.result; if (!db.objectStoreNames.contains(storeName)) { console.log(`Creating ${storeName} store`); db.createObjectStore(storeName, { keyPath: 'id' }); } }; request.onsuccess = (_event) => { console.log(`Store ${storeName} created successfully`); // 未关闭数据库连接 }; request.onerror = (event) => { console.error('Error creating store', event); }; }; // 向IndexedDB添加数据 export const addData = <T>(storeName: StoreName, data: T) => { // 打开数据库连接 request = window.indexedDB.open(DBName.Main, version); request.onsuccess = (_event) => { console.log('request.onsuccess'); db = request.result; const tx = db.transaction(storeName, 'readwrite'); const store = tx.objectStore(storeName); store.add(data); }; };
类型定义
export enum DBName { Main = 'main', } export enum StoreName { Users = 'users', Posts = 'posts', News = 'news', }
组件调用
// someComponent.tsx import { StoreName, addData, initDB, createStore, deleteDB } from "../lib/iddb"; <button onClick={() => initDB()}>初始化数据库</button> <button onClick={() => deleteDB()}>删除数据库</button> <button onClick={() => createStore(StoreName.News)}>创建News仓库</button>
问题分析
我的理解:
- 添加对象仓库:提升数据库版本并打开新连接,在
onupgradeneeded中处理仓库创建 - 执行CRUD操作:打开数据库,在
onsuccess中执行事务
遇到的问题:当数据库已存在且有未关闭的连接时,IDBDatabase的监听器不会触发,必须刷新页面才能正常创建对象仓库。如果不给addData指定版本,功能能正常运行,但提升版本打开新连接会破坏CRUD操作,且必须刷新页面才能创建新仓库。
解决方案
问题核心在于未正确关闭数据库连接,以及全局变量request、db被重复覆盖导致的连接冲突。IndexedDB在有未关闭的连接时,版本升级请求会被挂起,直到所有旧连接关闭。
修正后的代码
let db: IDBDatabase | null = null; let currentVersion = 1; // 获取数据库连接的工具函数 const getDBConnection = (dbName: string, version?: number): Promise<IDBDatabase> => { return new Promise((resolve, reject) => { const request = version !== undefined ? window.indexedDB.open(dbName, version) : window.indexedDB.open(dbName); request.onupgradeneeded = (event) => { const db = request.result; currentVersion = db.version; }; request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event); }; }); }; export const initDB = async () => { console.log('initDB'); let db = await getDBConnection(DBName.Main); // 检查是否需要创建默认仓库 if (!db.objectStoreNames.contains(StoreName.Users) || !db.objectStoreNames.contains(StoreName.Posts)) { const newVersion = db.version + 1; db.close(); const upgradedDb = await getDBConnection(DBName.Main, newVersion); if (!upgradedDb.objectStoreNames.contains(StoreName.Users)) { console.log(`Creating ${StoreName.Users} store`); upgradedDb.createObjectStore(StoreName.Users, { keyPath: 'id' }); } if (!upgradedDb.objectStoreNames.contains(StoreName.Posts)) { console.log(`Creating ${StoreName.Posts} store`); upgradedDb.createObjectStore(StoreName.Posts, { keyPath: 'id' }); } upgradedDb.close(); // 重新打开连接 db = await getDBConnection(DBName.Main); } currentVersion = db.version; db.close(); }; export const deleteDB = () => { const request = window.indexedDB.deleteDatabase(DBName.Main); request.onsuccess = () => { console.log('数据库删除成功'); currentVersion = 1; }; }; export const createStore = async (storeName: StoreName) => { const existingDb = await getDBConnection(DBName.Main); if (existingDb.objectStoreNames.contains(storeName)) { console.log(`仓库${storeName}已存在`); existingDb.close(); return; } const newVersion = existingDb.version + 1; existingDb.close(); // 必须关闭旧连接才能升级版本 const upgradedDb = await getDBConnection(DBName.Main, newVersion); console.log(`Creating ${storeName} store`); upgradedDb.createObjectStore(storeName, { keyPath: 'id' }); upgradedDb.close(); currentVersion = newVersion; console.log(`仓库${storeName}创建成功`); }; export const addData = async <T>(storeName: StoreName, data: T) => { const db = await getDBConnection(DBName.Main); const tx = db.transaction(storeName, 'readwrite'); const store = tx.objectStore(storeName); await store.add(data); tx.oncomplete = () => { console.log('数据添加成功'); db.close(); }; tx.onerror = (event) => { console.error('数据添加失败', event); db.close(); }; };
关键修复点
- 用Promise封装操作:避免回调嵌套,清晰控制连接生命周期
- 强制关闭连接:每次操作完成后关闭连接,防止版本升级请求被挂起
- 避免全局变量冲突:不再重复覆盖全局
request,改用局部变量和Promise管理连接 - 版本升级前置关闭:提升版本前必须关闭所有现有连接,确保
onupgradeneeded正常触发
内容的提问来源于stack exchange,提问作者Fer Toasted
相关产品推荐
相关产品推荐

