一、IndexedDB:浏览器端的NoSQL数据库
IndexedDB(Indexed Database API)是浏览器内置的事务型NoSQL数据库系统,专为客户端存储大量结构化数据而设计。与传统的localStorage相比,IndexedDB提供了更强大的功能和更好的性能表现。
核心特性
大容量存储:IndexedDB几乎没有存储上限,通常可存储50MB到数百MB的数据,远超localStorage的5MB限制。这使其成为存储大型应用状态、离线数据和媒体资源的理想选择。
异步操作:所有操作都是异步的,不会阻塞主线程,确保页面流畅性。在处理超过500KB数据时,IndexedDB的性能优势尤为明显,页面响应性能可提升40%以上。
事务支持:提供原子性操作机制,确保数据操作的完整性和一致性。在复杂操作(如转账类操作)时非常关键。
结构化数据存储:支持存储JavaScript对象、Blob、ArrayBuffer等二进制数据,无需手动序列化。同时支持索引和复杂查询,可实现按字段筛选、排序、范围查询等高级操作。
适用场景
- 离线优先应用(PWA):在用户离线时完整保存应用数据,网络恢复后同步到服务器
- 富文本编辑器/复杂表单:频繁静默保存用户输入内容,即使浏览器崩溃也能恢复
- 大型应用数据缓存:首次加载后存入本地,后续访问优先从本地读取
- 客户端日志/分析数据持久化:批量存储用户行为日志,待网络良好时统一上报
二、Dexie.js:简化IndexedDB操作的利器
Dexie.js是一个轻量级的JavaScript库,专门用于简化IndexedDB的操作。它通过封装IndexedDB的复杂API,提供了更直观、易用的接口,使开发者能够更高效地进行前端持久化数据存储。
核心优势
极简API设计:Dexie.js提供了简洁的链式调用API,大幅降低了代码量。原生IndexedDB需要10+行代码的事务操作,Dexie.js一行即可搞定。
Promise和Async/Await支持:所有接口都返回Promise,支持现代异步编程方式,避免回调地狱。
强大的查询能力:支持范围查询、多条件查询、复合索引、排序和分页等复杂操作,查询语法类似MongoDB。
事务管理:内置事务机制,确保多个数据库操作的原子性。
跨浏览器兼容性:兼容Chrome、Firefox、Safari、Edge等主流现代浏览器。
安装方式
bash
# npm安装
npm install dexie
三、Vue3中使用Dexie.js
基础配置
首先在项目中创建数据库配置文件:
javascript
// src/utils/db.js
import Dexie from 'dexie'
const db = new Dexie('MyVueAppDB')
// 定义数据库表和索引
db.version(1).stores({
users: '++id, name, age, email',
posts: '++id, title, content, userId, createdAt'
})
export default db
组合式API封装
javascript
// src/composables/useUsers.js
import { ref } from 'vue'
import db from '@/utils/db'
export function useUsers() {
const users = ref([])
const loading = ref(false)
const error = ref(null)
// 获取所有用户
const fetchUsers = async () => {
loading.value = true
try {
users.value = await db.users.toArray()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 添加用户
const addUser = async (userData) => {
try {
const id = await db.users.add(userData)
await fetchUsers() // 重新获取数据
return id
} catch (err) {
error.value = err.message
throw err
}
}
// 更新用户
const updateUser = async (id, updates) => {
try {
await db.users.update(id, updates)
await fetchUsers()
} catch (err) {
error.value = err.message
throw err
}
}
// 删除用户
const deleteUser = async (id) => {
try {
await db.users.delete(id)
await fetchUsers()
} catch (err) {
error.value = err.message
throw err
}
}
// 复杂查询:按年龄范围查询
const getUsersByAgeRange = async (minAge, maxAge) => {
try {
return await db.users
.where('age')
.between(minAge, maxAge)
.toArray()
} catch (err) {
error.value = err.message
throw err
}
}
return {
users,
loading,
error,
fetchUsers,
addUser,
updateUser,
deleteUser,
getUsersByAgeRange
}
}
在组件中使用
js
<template>
<div>
<h2>用户列表</h2>
<div v-if="loading">加载中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.age }}岁
<button @click="deleteUser(user.id)">删除</button>
</li>
</ul>
</div>
<form @submit.prevent="addNewUser">
<input v-model="newUser.name" placeholder="姓名" required>
<input v-model.number="newUser.age" type="number" placeholder="年龄" required>
<input v-model="newUser.email" type="email" placeholder="邮箱">
<button type="submit">添加用户</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useUsers } from '@/composables/useUsers'
const { users, loading, error, fetchUsers, addUser, deleteUser } = useUsers()
const newUser = ref({
name: '',
age: '',
email: ''
})
onMounted(() => {
fetchUsers()
})
const addNewUser = async () => {
try {
await addUser(newUser.value)
newUser.value = { name: '', age: '', email: '' }
} catch (err) {
console.error('添加用户失败:', err)
}
}
</script>
实时查询(Live Query)
Dexie.js提供了实时查询功能,当数据库数据变化时自动更新UI:
javascript
// 使用实时查询
import { liveQuery } from "dexie";
// 在Vue3中需要额外处理
import { from } from '@vueuse/rxjs'
import { useObservable } from '@vueuse/rxjs'
const users = useObservable(
from(
liveQuery(async () => {
return await db.users.toArray()
})
)
)
四、React中使用Dexie.js
安装依赖
bash
npm install dexie dexie-react-hooks
数据库配置
javascript
// src/db.js
import Dexie from 'dexie'
class AppDatabase extends Dexie {
constructor() {
super('MyReactAppDB')
this.version(1).stores({
todos: '++id, title, completed, createdAt',
users: '++id, name, email, age'
})
this.todos = this.table('todos')
this.users = this.table('users')
}
}
export const db = new AppDatabase()
自定义Hook封装
javascript
// src/hooks/useTodos.js
import { useState, useEffect } from 'react'
import { useLiveQuery } from 'dexie-react-hooks'
import { db } from '../db'
export function useTodos() {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
// 使用useLiveQuery实现实时查询
const todos = useLiveQuery(
() => db.todos.toArray(),
[],
[]
)
const addTodo = async (title) => {
setLoading(true)
try {
await db.todos.add({
title,
completed: false,
createdAt: new Date()
})
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
const toggleTodo = async (id, completed) => {
try {
await db.todos.update(id, { completed })
} catch (err) {
setError(err.message)
}
}
const deleteTodo = async (id) => {
try {
await db.todos.delete(id)
} catch (err) {
setError(err.message)
}
}
const clearCompleted = async () => {
try {
await db.todos.where('completed').equals(true).delete()
} catch (err) {
setError(err.message)
}
}
return {
todos: todos || [],
loading,
error,
addTodo,
toggleTodo,
deleteTodo,
clearCompleted
}
}
组件中使用
js
// src/components/TodoList.jsx
import React, { useState } from 'react'
import { useTodos } from '../hooks/useTodos'
function TodoList() {
const { todos, loading, error, addTodo, toggleTodo, deleteTodo, clearCompleted } = useTodos()
const [newTodoTitle, setNewTodoTitle] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
if (newTodoTitle.trim()) {
addTodo(newTodoTitle.trim())
setNewTodoTitle('')
}
}
if (loading) return <div>加载中...</div>
if (error) return <div>错误: {error}</div>
return (
<div>
<h2>待办事项</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodoTitle}
onChange={(e) => setNewTodoTitle(e.target.value)}
placeholder="添加新待办事项"
/>
<button type="submit">添加</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id, !todo.completed)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.title}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
<button onClick={clearCompleted}>清除已完成</button>
</div>
)
}
export default TodoList
复杂查询示例
javascript
// 范围查询:查询年龄在20-30岁之间的用户
const youngUsers = await db.users
.where('age')
.between(20, 30)
.toArray()
// 多条件查询:查询特定类别且价格小于200的商品
const results = await db.items
.where('category')
.equals('A')
.and(item => item.price < 200)
.toArray()
// 排序和分页
const paginatedResults = await db.items
.orderBy('price')
.offset(10) // 跳过前10条
.limit(5) // 获取5条
.toArray()
五、最佳实践与性能优化
1. 合理设计索引
为高频查询字段创建索引,避免全表扫描:
javascript
db.version(1).stores({
products: '++id, name, price, category, [category+price]'
})
2. 批量操作优化
使用批量操作API提高性能:
javascript
// 批量添加
await db.users.bulkAdd([
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 28 }
])
// 批量更新
await db.users.bulkPut([
{ id: 1, name: 'Alice Smith', age: 26 },
{ id: 2, name: 'Bob Johnson', age: 31 }
])
3. 事务优化
将相关操作放在同一事务中执行:
javascript
await db.transaction('rw', db.users, db.posts, async () => {
const userId = await db.users.add({ name: 'John', age: 25 })
await db.posts.add({ title: 'Hello World', content: '...', userId })
})
4. 错误处理
javascript
try {
await db.users.add({ name: 'Alice', age: 25 })
} catch (error) {
if (error.name === 'ConstraintError') {
console.error('数据约束错误:', error.message)
} else {
console.error('未知错误:', error)
}
}
5. 数据库版本升级
javascript
db.version(2).stores({
users: '++id, name, age, email, city' // 新增city字段
})
db.version(3).upgrade(trans => {
return trans.table('users').toCollection().modify(user => {
// 为已有用户添加默认邮箱
if (!user.email) {
user.email = `${user.name.toLowerCase()}@example.com`
}
})
})
六、总结
IndexedDB与Dexie.js的组合为前端开发提供了强大的本地数据存储解决方案。IndexedDB作为浏览器内置的NoSQL数据库,提供了大容量存储、异步操作和事务支持等核心能力;而Dexie.js通过极简的API设计,大幅降低了IndexedDB的使用门槛。
在Vue3和React中,通过合理的封装和Hook设计,可以实现响应式的数据管理,结合实时查询功能,能够构建出真正离线优先的Web应用。无论是简单的待办事项应用,还是复杂的企业级系统,IndexedDB + Dexie.js都能提供可靠的数据存储方案。
适用场景总结:
- ✅ 需要离线功能的PWA应用
- ✅ 存储大量结构化数据(10MB以上)
- ✅ 需要复杂查询和索引的场景
- ✅ 离线优先的数据同步应用
- ❌ 简单的键值对存储(推荐localStorage)
- ❌ 临时会话数据(推荐sessionStorage)