抛弃 localStorage:下一代存储方案的性能与安全革命

一、传统存储方案的致命缺陷

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的优势,如更大的存储容量和支持复杂数据结构。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容