IndexedDB 是一种低级别的客户端存储技术,允许在浏览器中存储大量结构化数据,并支持索引查询。与传统的 Web 存储(如 LocalStorage 和 SessionStorage)相比,IndexedDB 的数据存储容量更大、查询更灵活,适用于存储大型数据集合。
IndexedDB 基于键值对存储数据,支持对象存储(类似数据库的表)。每个对象存储可以有多个索引(类似数据库的索引),用于加速查询。
1. 基本概念
- 数据库(Database):包含数据的容器。
- 对象存储(Object Store):类似数据库的“表”,存储实际的数据记录。
- 事务(Transaction):在同一个数据库操作中,事务保证操作的一致性。
- 索引(Index):通过某些字段(如产品名称)为对象存储创建索引,加速查询。
- 游标(Cursor):允许按顺序遍历对象存储中的数据
2. IndexedDB 基本操作
2.1 打开数据库
打开(或创建)一个数据库,定义版本和对象存储结构。如果数据库版本发生变化,浏览器会触发 onupgradeneeded
事件来升级数据库。
// indexedDB.js
export const openDb = () => {
const dbName = 'productDatabase'; // 数据库名称
const version = 1; // 数据库版本(升级时会更新)
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, version);
request.onsuccess = (event) => {
resolve(event.target.result); // 返回数据库实例
};
request.onerror = (event) => {
reject(event.target.error); // 错误处理
};
// 数据库版本变化时会触发onupgradeneeded事件
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建存储对象,如果表格(objectStore)已经存在,则不再创建
if (!db.objectStoreNames.contains('products')) {
db.createObjectStore('products', { keyPath: 'id', autoIncrement: true });
}
};
});
};
indexedDB.open()
方法用于打开数据库。onsuccess
事件返回数据库实例。onerror
事件处理打开数据库失败。onupgradeneeded
事件触发数据库结构变化时,可以在这里创建对象存储(类似创建数据库表)。transaction.objectStore('products')
获取一个对象存储(表)。put()
方法用于插入或更新数据。oncomplete
和onerror
事件处理事务成功或失败。
2.2 查询数据
使用 get()
或 getAll()
方法获取数据。get()
根据主键获取单个数据,getAll()
获取所有数据。
// dbOperations.js
import { openDb } from './indexDB';
// 增加或更新一个产品
export const addProduct = async (product) => {
const db = await openDb(); // 打开数据库
const transaction = db.transaction('products', 'readwrite'); // 以读写模式开启事务
const store = transaction.objectStore('products'); // 获取 'products' 对象存储
store.put(product); // put会根据主键是否存在决定是更新还是插入
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve('Product added/updated successfully');
transaction.onerror = (e) => reject(e.target.error);
});
};
// 获取所有产品
export const getAllProducts = async () => {
const db = await openDb(); // 打开数据库
const transaction = db.transaction('products', 'readonly'); // 以只读模式开启事务
const store = transaction.objectStore('products');
const request = store.getAll(); // 获取所有产品
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = (e) => reject(e.target.error);
});
};
// 删除一个产品
export const deleteProduct = async (id) => {
const db = await openDb();
const transaction = db.transaction('products', 'readwrite');
const store = transaction.objectStore('products');
store.delete(id); // 删除指定ID的产品
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve('Product deleted successfully');
transaction.onerror = (e) => reject(e.target.error);
});
};
// 更新产品
export const updateProduct = async (product) => {
return addProduct(product); // 更新功能和添加一样,直接调用addProduct
};
// 根据ID查询数据
const getProductById = async (id) => {
const db = await openDb(); // 打开数据库
const transaction = db.transaction('products', 'readonly'); // 只读事务
const store = transaction.objectStore('products'); // 获取 products 对象存储
const request = store.get(id); // 使用 get 方法根据 ID 查询数据
return new Promise((resolve, reject) => {
request.onsuccess = () => {
// 如果数据存在,返回数据,否则返回 null
resolve(request.result || null);
};
request.onerror = (e) => reject(e.target.error); // 错误处理
});
};
3. IndexedDB 使用场景
IndexedDB 适用于需要存储大量、结构化数据的场景,常见的使用案例包括:
- 离线应用:当网络不可用时,可以利用 IndexedDB 存储用户数据和应用状态。
- 本地缓存:用于在本地存储从服务器请求的数据,提高页面加载速度。
- 大数据存储:例如,大量日志数据、图片、文件等,可以直接存储到浏览器中。
4. IndexedDB API 小结
- open():打开数据库。
- transaction():开启事务。
- objectStore():访问对象存储(表)。
- put():插入或更新数据。
- get():获取指定主键的数据。
- getAll():获取所有数据。
- delete():删除数据。
- onsuccess/onerror:处理成功和失败的回调。
5. 完整的 IndexedDB 封装示例
将上述操作封装成一个完整的模块,方便在 Vue.js 中调用:
<script>
import { getAllProducts, addProduct, deleteProduct, updateProduct } from './dbOperations';
export default {
data() {
return {
products: [],
filterQuery: '',
showAddModal: false,
editingProduct: null,
newProduct: {
name: '',
price: '',
},
};
},
computed: {
filteredProducts() {
return this.products
.filter((product) =>
product.name.toLowerCase().includes(this.filterQuery.toLowerCase())
)
.sort((a, b) => {
if (this.sortKey === 'name') {
return a.name.localeCompare(b.name);
} else if (this.sortKey === 'price') {
return a.price - b.price;
}
return 0;
});
},
},
methods: {
async loadProducts() {
try {
const products = await getAllProducts();
console.log('Products loaded:', products);
this.products = products;
} catch (error) {
console.error('Error loading products:', error);
}
},
async saveProduct() {
try {
await addProduct(this.newProduct);
this.loadProducts(); // 重新加载数据
this.cancelEdit();
} catch (error) {
console.error('Error saving product:', error);
}
},
async editProduct(product) {
this.newProduct = { ...product };
this.editingProduct = product;
this.showAddModal = true;
},
async deleteProduct(id) {
try {
await deleteProduct(id);
this.loadProducts(); // 重新加载数据
} catch (error) {
console.error('Error deleting product:', error);
}
},
cancelEdit() {
this.newProduct = { name: '', price: '' };
this.editingProduct = null;
this.showAddModal = false;
},
},
mounted() {
this.loadProducts(); // 页面加载时从IndexedDB获取数据
},
};
</script>
通过这些操作,你可以非常方便地从 IndexedDB 中查询数据,并在 Vue.js 中展示和编辑数据。