前端 IndexDB 使用指南
目录
IndexedDB 一句话理解
浏览器里的大容量本地数据库,比 localStorage 能存更多、更复杂的数据。
📊 对比 localStorage
| 特性 | localStorage | IndexedDB |
|---|---|---|
| 容量 | 5MB | 至少250MB,甚至GB级 |
| 数据类型 | 字符串 | 任何JS对象、文件、二进制 |
| 查询方式 | 键值对 | 键值、索引、范围查询 |
| 速度 | 同步(阻塞) | 异步(不阻塞页面) |
🎯 使用场景(什么时候用?)
用 localStorage:
- 存 token、用户设置等小数据
- 简单键值对,数据量 < 5MB
用 IndexedDB:
- 离线应用的全部数据
- 用户生成的内容(图片、笔记)
- 应用缓存(大量API数据)
- 编辑器草稿自动保存
💡 使用示例
// 存文件/图片 await db.put('files', { id: 'photo1', name: '头像.jpg', data: fileBlob, // 二进制文件 size: '2MB' }); // 复杂查询(localStorage做不到) const expensiveProducts = await db .getAllFromIndex('products', 'price', IDBKeyRange.lowerBound(1000));⚠️ 注意事项
不要用 IndexedDB 存:
- 密码等敏感信息(不安全)
- 实时同步数据(用 WebSocket)
- 服务器端数据(用API)
应该用 IndexedDB 存:
- 离线可用的用户数据
- 大型应用的本地缓存
- 用户上传的文件本地副本
一句话总结:需要存大量复杂数据且支持离线访问时,用 IndexedDB。
IDB 完整使用指南
1. 安装与基础操作
安装
yarn add idb四个核心方法
import { openDB } from 'idb'; // 1. 打开/创建数据库(首次运行时会自动创建) const db = await openDB('MyAppDB', 1, { // 参数:数据库名,版本号 upgrade(db) { // 只在版本更新或初次创建时执行 // 创建对象仓库(相当于数据库表) db.createObjectStore('tasks', { keyPath: 'id' // 指定主键为 id 字段 }); } }); // 2. 添加或更新数据 await db.put('tasks', { id: 1, // 必须有 id 字段,作为主键 title: '学习 IDB', completed: false }); // 3. 查询所有数据 const allTasks = await db.getAll('tasks'); // 4. 根据 id 查询单条 const task = await db.get('tasks', 1); // 5. 根据 id 删除 await db.delete('tasks', 1);2. 完整工具类封装
import { openDB } from 'idb'; class TaskDB { constructor() { this.dbName = 'TaskDatabase'; this.storeName = 'tasks'; this.version = 1; } // 初始化数据库连接 async connect() { return openDB(this.dbName, this.version, { upgrade(db) { // 如果 tasks 表不存在就创建 if (!db.objectStoreNames.contains('tasks')) { db.createObjectStore('tasks', { keyPath: 'id' // 主键字段 }); } } }); } // 增/改:保存任务 async saveTask(task) { const db = await this.connect(); // put 方法:id 存在则更新,不存在则新增 return db.put(this.storeName, task); } // 删:删除任务 async deleteTask(id) { const db = await this.connect(); return db.delete(this.storeName, id); } // 查:获取所有任务 async getAllTasks() { const db = await this.connect(); return db.getAll(this.storeName); } // 查:根据 id 获取单个任务 async getTask(id) { const db = await this.connect(); return db.get(this.storeName, id); } } export default new TaskDB(); // 导出单例实例3. React 组件中使用
import React, { useState, useEffect } from 'react'; import taskDB from './taskDB'; // 导入上面封装的数据库工具 function TaskManager() { const [tasks, setTasks] = useState([]); const [newTaskTitle, setNewTaskTitle] = useState(''); // 组件加载时从数据库读取所有任务 useEffect(() => { loadTasks(); }, []); // 从数据库加载任务 const loadTasks = async () => { const savedTasks = await taskDB.getAllTasks(); setTasks(savedTasks || []); // 处理空数据情况 }; // 添加新任务 const handleAddTask = async () => { if (!newTaskTitle.trim()) return; const newTask = { id: Date.now(), // 使用时间戳作为唯一 ID title: newTaskTitle, completed: false, createdAt: new Date().toISOString() }; // 1. 保存到数据库 await taskDB.saveTask(newTask); // 2. 更新界面状态 setTasks([...tasks, newTask]); setNewTaskTitle(''); }; // 切换任务完成状态 const handleToggleTask = async (task) => { const updatedTask = { ...task, completed: !task.completed }; // 1. 更新数据库 await taskDB.saveTask(updatedTask); // 2. 更新界面状态 setTasks(tasks.map(t => t.id === task.id ? updatedTask : t )); }; // 删除任务 const handleDeleteTask = async (taskId) => { // 1. 从数据库删除 await taskDB.deleteTask(taskId); // 2. 从界面状态删除 setTasks(tasks.filter(task => task.id !== taskId)); }; return ( <div className="task-manager"> <h1>任务管理器</h1> {/* 添加任务表单 */} <div className="add-task"> <input type="text" value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} placeholder="输入新任务..." /> <button onClick={handleAddTask}>添加</button> </div> {/* 任务列表 */} <ul className="task-list"> {tasks.map(task => ( <li key={task.id} className={task.completed ? 'completed' : ''}> <input type="checkbox" checked={task.completed} onChange={() => handleToggleTask(task)} /> <span>{task.title}</span> <button onClick={() => handleDeleteTask(task.id)} className="delete-btn" > 删除 </button> </li> ))} </ul> {/* 统计信息 */} <div className="stats"> 总计: {tasks.length} 个任务 | 已完成: {tasks.filter(t => t.completed).length} | 未完成: {tasks.filter(t => !t.completed).length} </div> </div> ); } export default TaskManager;4. 关键概念说明
📌 必知要点
- keyPath: 'id':指定数据的主键字段,每条数据必须有唯一的 id
- put() vs add():
put():id 存在则更新,不存在则新增add():只能新增,id 重复会报错
- 版本控制:修改数据库结构时需增加版本号
// 版本升级示例 openDB('MyDB', 2, { // 版本从 1 升到 2 upgrade(db, oldVersion) { if (oldVersion < 1) { // 初始创建 db.createObjectStore('tasks', { keyPath: 'id' }); } if (oldVersion < 2) { // 新增索引(可按标题搜索) const store = db.transaction.objectStore('tasks'); store.createIndex('title', 'title'); } } });5. 官方资源
- GitHub 仓库:jakearchibald/idb
- 完整 API 文档:仓库中的 README 文件
- 在线体验:IDB 示例
6. 故障排除
// 1. 确保所有操作都是异步的 try { const db = await openDB(...); await db.put(...); } catch (error) { console.error('数据库错误:', error); } // 2. 检查浏览器兼容性 if (!window.indexedDB) { alert('您的浏览器不支持 IndexedDB'); } // 3. 清除缓存(开发时有用) indexedDB.deleteDatabase('MyAppDB');💡 一句话总结:openDB 打开数据库,put 保存数据,get 查询数据,delete 删除数据,所有操作都要加 await。