一、传统存储方案的致命缺陷
1.安全漏洞
localStorage 数据明文存储,易被XSS攻击窃取
同源策略无法阻止恶意脚本读取敏感信息
2.性能瓶颈
同步阻塞机制导致主线程卡顿
5MB存储上限无法满足现代应用需求
二、IndexedDB:下一代存储解决方案
核心优势:
✅ 异步非阻塞:通过Web Workers实现后台操作,零界面卡顿
✅ 加密存储:支持Web Crypto API端到端加密
✅ 海量存储:理论无上限(浏览器配额动态调整)
渐进式迁移方案
1.创建IndexedDB封装 :
- 在 src/utils/storage.ts 中实现了 IndexedDBStorage 类,提供了类似localStorage的API接口
- 封装了基本的CRUD操作: setItem 、 getItem 、 removeItem 、 clear
- 添加了高级功能: keys() 获取所有键名、 length() 获取存储项数量
- 支持存储复杂JSON数据,自动处理序列化和反序列化
- 导出了默认的storage实例,方便直接使用
/**
* IndexedDB封装,提供类似localStorage的API接口
*/
interface DBConfig {
dbName: string;
storeName: string;
version: number;
}
class IndexedDBStorage {
private dbName: string;
private storeName: string;
private version: number;
private db: IDBDatabase | null = null;
constructor(config: DBConfig) {
this.dbName = config.dbName;
this.storeName = config.storeName;
this.version = config.version;
}
/**
* 打开数据库连接
*/
private async openDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
if (this.db) {
resolve(this.db);
return;
}
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
const db = (event.target as IDBOpenDBRequest).result;
// 检查存储对象是否已存在
if (!db.objectStoreNames.contains(this.storeName)) {
// 创建存储对象,使用keyPath作为主键
db.createObjectStore(this.storeName, { keyPath: 'key' });
}
};
request.onsuccess = (event: Event) => {
this.db = (event.target as IDBOpenDBRequest).result;
resolve(this.db);
};
request.onerror = (event: Event) => {
reject((event.target as IDBOpenDBRequest).error);
};
});
}
/**
* 获取事务和存储对象
*/
private async getStore(mode: IDBTransactionMode): Promise<IDBObjectStore> {
const db = await this.openDB();
const transaction = db.transaction([this.storeName], mode);
return transaction.objectStore(this.storeName);
}
/**
* 存储数据
* @param key 键名
* @param value 键值
*/
async setItem(key: string, value: any): Promise<void> {
try {
const store = await this.getStore('readwrite');
// 序列化复杂数据类型
const data = {
key,
value: typeof value === 'object' ? JSON.stringify(value) : value,
timestamp: Date.now()
};
await new Promise<void>((resolve, reject) => {
const request = store.put(data);
request.onsuccess = () => resolve();
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
} catch (error) {
console.error('IndexedDB setItem error:', error);
throw error;
}
}
/**
* 获取数据
* @param key 键名
*/
async getItem<T = any>(key: string): Promise<T | null> {
try {
const store = await this.getStore('readonly');
const result = await new Promise<any>((resolve, reject) => {
const request = store.get(key);
request.onsuccess = (event) => resolve((event.target as IDBRequest).result);
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
if (!result) return null;
// 尝试解析JSON字符串
try {
return JSON.parse(result.value);
} catch {
// 如果解析失败,返回原始值
return result.value as T;
}
} catch (error) {
console.error('IndexedDB getItem error:', error);
return null;
}
}
/**
* 删除数据
* @param key 键名
*/
async removeItem(key: string): Promise<void> {
try {
const store = await this.getStore('readwrite');
await new Promise<void>((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
} catch (error) {
console.error('IndexedDB removeItem error:', error);
throw error;
}
}
/**
* 清除所有数据
*/
async clear(): Promise<void> {
try {
const store = await this.getStore('readwrite');
await new Promise<void>((resolve, reject) => {
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
} catch (error) {
console.error('IndexedDB clear error:', error);
throw error;
}
}
/**
* 获取所有键名
*/
async keys(): Promise<string[]> {
try {
const store = await this.getStore('readonly');
const keys: string[] = [];
await new Promise<void>((resolve, reject) => {
const request = store.openCursor();
request.onsuccess = (event) => {
const cursor = (event.target as IDBRequest).result;
if (cursor) {
keys.push(cursor.key as string);
cursor.continue();
} else {
resolve();
}
};
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
return keys;
} catch (error) {
console.error('IndexedDB keys error:', error);
return [];
}
}
/**
* 获取数据库中存储的项目数量
*/
async length(): Promise<number> {
try {
const store = await this.getStore('readonly');
const count = await new Promise<number>((resolve, reject) => {
const request = store.count();
request.onsuccess = (event) => resolve((event.target as IDBRequest).result);
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
return count;
} catch (error) {
console.error('IndexedDB length error:', error);
return 0;
}
}
/**
* 关闭数据库连接
*/
close(): void {
if (this.db) {
this.db.close();
this.db = null;
}
}
/**
* 删除数据库
*/
async deleteDatabase(): Promise<void> {
this.close();
return new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(this.dbName);
request.onsuccess = () => resolve();
request.onerror = (event) => reject((event.target as IDBOpenDBRequest).error);
});
}
}
// 创建默认实例
export const storage = new IndexedDBStorage({
dbName: 'app-database',
storeName: 'key-value-store',
version: 1
});
export default IndexedDBStorage;
在其他组件中,可以通过以下方式使用IndexedDB存储
import { storage } from '@/utils';
// 存储数据
await storage.setItem('userInfo', { name: '张三', age: 25 });
// 获取数据
const userInfo = await storage.getItem<{name: string, age: number}>('userInfo');
// 删除数据
await storage.removeItem('userInfo');
// 清空所有数据
await storage.clear();
这个封装让IndexedDB的使用变得像localStorage一样简单,同时充分利用了IndexedDB的优势,如更大的存储容量和支持复杂数据结构。