Vue 中使用 Dexie.js
Dexie.js 是一个优秀的 IndexedDB 封装库,结合 Vue 可以轻松实现客户端数据存储。
1. 安装与初始化
安装 Dexie.js
npm install dexie
# 或
yarn add dexie
创建数据库服务文件
在 src
目录下创建 db.js
文件:
import Dexie from 'dexie';const db = new Dexie('VueDatabase');// 定义数据库结构
db.version(1).stores({todos: '++id, title, completed, createdAt',notes: '++id, content, tags, updatedAt'
});// 可选:添加示例数据
db.on('populate', () => {db.todos.add({ title: '学习Dexie.js', completed: false, createdAt: new Date() });db.notes.add({ content: '这是我的第一条笔记', tags: ['重要'], updatedAt: new Date() });
});export default db;
2. 在 Vue 组件中使用
基本 CRUD 操作
<template><div><h2>待办事项</h2><input v-model="newTodo" @keyup.enter="addTodo" placeholder="添加新任务"><ul><li v-for="todo in todos" :key="todo.id"><input type="checkbox" v-model="todo.completed" @change="updateTodo(todo)"><span :class="{ completed: todo.completed }">{{ todo.title }}</span><button @click="deleteTodo(todo.id)">删除</button></li></ul></div>
</template><script>
import db from '@/db';export default {data() {return {todos: [],newTodo: ''};},async created() {await this.loadTodos();},methods: {async loadTodos() {this.todos = await db.todos.toArray();},async addTodo() {if (!this.newTodo.trim()) return;await db.todos.add({title: this.newTodo,completed: false,createdAt: new Date()});this.newTodo = '';await this.loadTodos();},async updateTodo(todo) {await db.todos.update(todo.id, {completed: todo.completed});},async deleteTodo(id) {await db.todos.delete(id);await this.loadTodos();}}
};
</script><style>
.completed {text-decoration: line-through;color: #888;
}
</style>
3. 使用 Vue 插件形式
为了在整个应用中更方便地使用数据库,可以创建 Vue 插件:
创建插件 src/plugins/dexie.js
import Dexie from 'dexie';const VueDexie = {install(Vue) {const db = new Dexie('VueAppDB');db.version(1).stores({settings: '++id, key, value',userData: '++id, userId, data'});Vue.prototype.$db = db;}
};export default VueDexie;
在 main.js 中注册插件
import Vue from 'vue';
import App from './App.vue';
import DexiePlugin from './plugins/dexie';Vue.use(DexiePlugin);new Vue({render: h => h(App)
}).$mount('#app');
在组件中使用
<script>
export default {async mounted() {// 使用插件提供的 $dbconst settings = await this.$db.settings.toArray();console.log(settings);// 添加设置await this.$db.settings.add({key: 'theme',value: 'dark'});}
};
</script>
4. 高级用法
响应式数据绑定
<template><div><h2>笔记列表</h2><div v-for="note in notes" :key="note.id"><h3>{{ note.content }}</h3><p>标签: {{ note.tags.join(', ') }}</p></div></div>
</template><script>
import { liveQuery } from 'dexie';
import db from '@/db';export default {data() {return {notes: []};},created() {// 使用 liveQuery 实现响应式数据this.subscription = liveQuery(() => db.notes.orderBy('updatedAt').reverse().toArray()).subscribe({next: notes => {this.notes = notes;},error: error => {console.error('查询错误:', error);}});},beforeDestroy() {// 组件销毁时取消订阅this.subscription && this.subscription.unsubscribe();}
};
</script>
复杂查询与分页
async function getPaginatedNotes(page = 1, pageSize = 10) {const offset = (page - 1) * pageSize;return await db.notes.orderBy('updatedAt').reverse().offset(offset).limit(pageSize).toArray();
}// 在组件中使用
export default {methods: {async loadNotes(page) {this.notes = await getPaginatedNotes(page);}}
};
事务处理
async function transferData(fromId, toId, amount) {await db.transaction('rw', db.userData, async () => {const fromUser = await db.userData.get(fromId);const toUser = await db.userData.get(toId);if (!fromUser || !toUser) {throw new Error('用户不存在');}if (fromUser.data.balance < amount) {throw new Error('余额不足');}await db.userData.update(fromId, {'data.balance': fromUser.data.balance - amount});await db.userData.update(toId, {'data.balance': toUser.data.balance + amount});});
}
5. 最佳实践
错误处理:始终处理数据库操作的错误
try {await db.someTable.add(data); } catch (error) {console.error('操作失败:', error);this.$toast.error('保存失败'); }
性能优化:
批量操作使用
bulkAdd
,bulkPut
,bulkDelete
大量数据时使用分页
合理创建索引
数据同步:
// 与后端API同步的示例 async function syncTodos() {const localTodos = await db.todos.toArray();const serverTodos = await api.getTodos();// 比较并合并数据的逻辑... }
4.数据库升级处理:
db.version(2).stores({todos: '++id, title, completed, createdAt, priority',notes: '++id, content, tags, updatedAt, isPinned' }).upgrade(tx => {return tx.table('todos').toCollection().modify(todo => {todo.priority = todo.priority || 'normal';}); });
6. 完整示例:Todo 应用
<template><div class="todo-app"><h1>Vue Dexie Todo</h1><div class="todo-form"><inputv-model="newTodo"@keyup.enter="addTodo"placeholder="输入任务并回车"><select v-model="priority"><option value="low">低优先级</option><option value="normal">普通</option><option value="high">高优先级</option></select><button @click="addTodo">添加</button></div><div class="filters"><button @click="filter = 'all'">全部</button><button @click="filter = 'active'">未完成</button><button @click="filter = 'completed'">已完成</button></div><ul class="todo-list"><li v-for="todo in filteredTodos" :key="todo.id" :class="{completed: todo.completed,'high-priority': todo.priority === 'high','low-priority': todo.priority === 'low'}"><inputtype="checkbox"v-model="todo.completed"@change="updateTodo(todo)"><span class="todo-title">{{ todo.title }}</span><span class="todo-date">{{ formatDate(todo.createdAt) }}</span><button @click="deleteTodo(todo.id)">删除</button></li></ul><div class="stats"><span>总计: {{ todos.length }} | 剩余: {{ activeCount }}</span><button v-if="completedCount > 0" @click="clearCompleted">清除已完成</button></div></div> </template><script> import db from '@/db'; import { liveQuery } from 'dexie';export default {data() {return {todos: [],newTodo: '',priority: 'normal',filter: 'all'};},created() {this.subscription = liveQuery(() => db.todos.orderBy('createdAt').toArray()).subscribe({next: todos => {this.todos = todos;},error: error => {console.error('加载待办事项失败:', error);}});},computed: {filteredTodos() {switch (this.filter) {case 'active':return this.todos.filter(t => !t.completed);case 'completed':return this.todos.filter(t => t.completed);default:return this.todos;}},activeCount() {return this.todos.filter(t => !t.completed).length;},completedCount() {return this.todos.filter(t => t.completed).length;}},methods: {formatDate(date) {return new Date(date).toLocaleDateString();},async addTodo() {if (!this.newTodo.trim()) return;try {await db.todos.add({title: this.newTodo,completed: false,priority: this.priority,createdAt: new Date()});this.newTodo = '';} catch (error) {console.error('添加任务失败:', error);}},async updateTodo(todo) {try {await db.todos.update(todo.id, {completed: todo.completed});} catch (error) {console.error('更新任务失败:', error);}},async deleteTodo(id) {try {await db.todos.delete(id);} catch (error) {console.error('删除任务失败:', error);}},async clearCompleted() {try {const completedIds = this.todos.filter(t => t.completed).map(t => t.id);await db.todos.bulkDelete(completedIds);} catch (error) {console.error('清除已完成任务失败:', error);}}},beforeDestroy() {this.subscription && this.subscription.unsubscribe();} }; </script><style> .todo-app {max-width: 600px;margin: 0 auto;padding: 20px; }.todo-form {display: flex;margin-bottom: 20px; }.todo-form input {flex-grow: 1;padding: 8px;margin-right: 10px; }.filters {margin-bottom: 20px; }.todo-list {list-style: none;padding: 0; }.todo-list li {display: flex;align-items: center;padding: 10px;border-bottom: 1px solid #eee; }.todo-title {flex-grow: 1;margin: 0 10px; }.todo-date {font-size: 0.8em;color: #888;margin-right: 10px; }.completed .todo-title {text-decoration: line-through;color: #888; }.high-priority {border-left: 3px solid red;padding-left: 7px; }.low-priority {opacity: 0.7; }.stats {margin-top: 20px;display: flex;justify-content: space-between;align-items: center; } </style>
7. 总结
在 Vue 中使用 Dexie.js 的关键点:
将数据库初始化代码封装为单独模块
使用
liveQuery
实现响应式数据绑定合理处理异步操作和错误
在组件销毁时取消订阅
对于复杂应用,考虑使用 Vuex/Pinia 管理状态
Dexie.js 与 Vue 的结合可以轻松实现离线优先的应用程序,提供良好的用户体验。