You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 20:30:44