Vue3结合Dexie.js前端数据库应用与实践

发布于:2025-09-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

概述

在现代Web开发中,随着单页面应用(SPA)的复杂性增加,对客户端数据存储的需求也越来越高。Vue3作为一款流行的前端框架,与轻量级浏览器数据库Dexie.js的结合,能够为开发者提供强大的数据管理能力,实现高效的离线存储和数据处理。

Dexie.js是IndexedDB的封装库,它简化了IndexedDB的使用难度,提供了直观的API来管理浏览器端数据库。本文将详细介绍如何在Vue3项目中集成和应用Dexie.js。

为什么选择IndexedDB和Dexie.js?

传统存储方式的局限性
当数据量较小时,SessionStorage和LocalStorage可以满足存储需求。但当数据量较大或需要更复杂的查询时,就完蛋了。IndexedDB是一种事务型数据库系统,允许存储和检索键值对对象,可以存储结构化克隆算法支持的任何对象。Dexie.js封装了IndexedDB的复杂API,提供更简洁的语法和Promise风格的异步操作,大大降低了学习成本和使用难度。它具有以下特点:

  • 大规模数据存储能力
  • 异步操作,不阻塞UI
  • 支持事务处理
  • 支持索引查询

在Vue3项目中安装和配置Dexie

安装Dexie.js

通过npm或yarn安装Dexie.js:

npm install dexie
# 或
yarn add dexie

创建数据库配置

在项目中创建db.js或db.ts文件配置数据库:

// db.js
import Dexie from 'dexie';

export const db = new Dexie('myDatabase');
// 定义数据库模式和索引
db.version(1).stores({
  friends: '++id, name, age', // 主键和索引属性
  projects: '++id, projectName, status'
});
对于TypeScript项目,可以使用更严格的类型定义:
// db.ts
import Dexie, { Table } from 'dexie';

// 定义接口
export interface Friend {
  id?: number;
  name: string;
  age: number;
}

export interface Project {
  id?: number;
  projectName: string;
  status: 'planned' | 'ongoing' | 'completed';
}

// 扩展Dexie类
export class AppDatabase extends Dexie {
  friends!: Table<Friend>;
  projects!: Table<Project>;

  constructor() {
    super('myDatabase');
    
    this.version(1).stores({
      friends: '++id, name, age',
      projects: '++id, projectName, status'
    });
  }
}

export const db = new AppDatabase();

Dexie.js基础操作

添加数据
// 添加单个朋友
const addFriend = async (friendData) => {
  try {
    const id = await db.friends.add(friendData);
    console.log(`朋友添加成功,ID: ${id}`);
    return id;
  } catch (error) {
    console.error('添加朋友失败:', error);
  }
};

// 使用示例
addFriend({ name: '张三', age: 25 });
查询数据

Dexie提供了丰富的查询API:

javascript
// 获取所有数据
const getAllFriends = async () => {
  return await db.friends.toArray();
};

// 根据ID获取单条数据
const getFriend = async (id) => {
  return await db.friends.get(id);
};

// 条件查询
const getYoungFriends = async () => {
  return await db.friends.where('age').below(30).toArray();
};

// 多条件查询
const getFriendByName = async (name) => {
  return await db.friends.where('name').equals(name).first();
};
更新数据
javascript
// 更新数据
const updateFriend = async (id, updates) => {
  try {
    const updated = await db.friends.update(id, updates);
    console.log(`更新了${updated}条数据`);
    return updated;
  } catch (error) {
    console.error('更新失败:', error);
  }
};

// 使用示例
updateFriend(1, { age: 26 });
删除数据
javascript
// 删除单条数据
const deleteFriend = async (id) => {
  try {
    await db.friends.delete(id);
    console.log('朋友删除成功');
  } catch (error) {
    console.error('删除失败:', error);
  }
};

// 批量删除
const deleteMultipleFriends = async (ids) => {
  try {
    await db.friends.bulkDelete(ids);
    console.log('批量删除成功');
  } catch (error) {
    console.error('批量删除失败:', error);
  }
};

// 清空表
const clearFriendsTable = async () => {
  try {
    await db.friends.clear();
    console.log('朋友表已清空');
  } catch (error) {
    console.error('清空表失败:', error);
  }
};

在Vue3组件中使用Dexie.js

组合式API用法

在Vue3的setup函数或

vue
<template>
  <div>
    <h2>朋友列表</h2>
    <ul>
      <li v-for="friend in friends" :key="friend.id">
        {{ friend.name }} - {{ friend.age }}<button @click="deleteFriend(friend.id)">删除</button>
      </li>
    </ul>
    
    <h2>添加新朋友</h2>
    <form @submit.prevent="addNewFriend">
      <input v-model="newFriend.name" type="text" placeholder="姓名" required>
      <input v-model="newFriend.age" type="number" placeholder="年龄" required>
      <button type="submit">添加朋友</button>
    </form>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { db } from '../db';

const friends = ref([]);
const newFriend = ref({
  name: '',
  age: ''
});

// 加载朋友列表
const loadFriends = async () => {
  friends.value = await db.friends.toArray();
};

// 添加新朋友
const addNewFriend = async () => {
  try {
    await db.friends.add({
      name: newFriend.value.name,
      age: parseInt(newFriend.value.age)
    });
    
    // 重置表单
    newFriend.value = { name: '', age: '' };
    
    // 重新加载列表
    await loadFriends();
  } catch (error) {
    console.error('添加朋友失败:', error);
  }
};

// 删除朋友
const deleteFriend = async (id) => {
  try {
    await db.friends.delete(id);
    await loadFriends(); // 重新加载列表
  } catch (error) {
    console.error('删除朋友失败:', error);
  }
};

// 组件挂载时加载数据
onMounted(() => {
  loadFriends();
});
</script>
使用Composition API封装存储逻辑

可以封装可复用的Dexie操作:

javascript
// composables/useFriends.js
import { ref, onMounted } from 'vue';
import { db } from '../db';

export function useFriends() {
  const friends = ref([]);
  const loading = ref(false);
  const error = ref(null);

  const loadFriends = async () => {
    loading.value = true;
    error.value = null;
    try {
      friends.value = await db.friends.toArray();
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  };

  const addFriend = async (friendData) => {
    try {
      const id = await db.friends.add(friendData);
      await loadFriends(); // 重新加载更新列表
      return id;
    } catch (err) {
      error.value = err.message;
      throw err;
    }
  };

  const deleteFriend = async (id) => {
    try {
      await db.friends.delete(id);
      await loadFriends(); // 重新加载更新列表
    } catch (err) {
      error.value = err.message;
      throw err;
    }
  };

  // 组件挂载时自动加载
  onMounted(() => {
    loadFriends();
  });

  return {
    friends,
    loading,
    error,
    loadFriends,
    addFriend,
    deleteFriend
  };
}

在组件中使用:

vue
<script setup>
import { useFriends } from '../composables/useFriends';

const { friends, loading, error, addFriend, deleteFriend } = useFriends();

// 直接使用暴露出来的方法和状态

高级用法

复杂查询和索引

Dexie支持丰富的查询方法:

javascript
// 范围查询
const getFriendsBetweenAges = async (minAge, maxAge) => {
  return await db.friends
    .where('age')
    .between(minAge, maxAge)
    .toArray();
};

// 前缀搜索
const getFriendsWithNamePrefix = async (prefix) => {
  return await db.friends
    .where('name')
    .startsWithIgnoreCase(prefix)
    .toArray();
};

// 多索引查询
const getFriendsByMultipleCriteria = async (name, maxAge) => {
  return await db.friends
    .where('name')
    .equals(name)
    .and(friend => friend.age <= maxAge)
    .toArray();
};
事务处理

Dexie支持事务,确保多个操作的原子性:

javascript
const transferFriend = async (fromId, toId, friendId) => {
  await db.transaction('rw', db.friends, db.projects, async () => {
    // 在此处执行多个操作,要么全部成功,要么全部失败
    const friend = await db.friends.get(friendId);
    await db.friends.delete(friendId);
    await db.projects.add({
      projectName: `Transfer ${friend.name}`,
      status: 'planned'
    });
  });
};
表关联查询

虽然Dexie是NoSQL数据库,但也可以实现类似关系型数据库的关联查询:

javascript
// 获取员工及其角色信息
const getEmployeesWithRoles = async () => {
  const employees = await db.employees.toArray();
  const roleIds = new Set(employees.flatMap(e => e.roleIds));
  const roles = await db.roles.bulkGet([...roleIds]);
  
  return employees.map(empl => ({
    ...empl,
    roles: empl.roleIds.map(id => roles.find(r => r.id === id))
  }));
};

性能优化

批量操作

使用Dexie的批量操作API提高性能:

javascript
// 批量添加
const addMultipleFriends = async (friendsArray) => {
  try {
    await db.friends.bulkAdd(friendsArray);
  } catch (error) {
    console.error('批量添加失败:', error);
  }
};

// 批量更新
const updateMultipleFriends = async (updatesArray) => {
  try {
    await db.friends.bulkPut(updatesArray);
  } catch (error) {
    console.error('批量更新失败:', error);
  }
};

// 批量删除
const deleteMultipleFriends = async (ids) => {
  try {
    await db.friends.bulkDelete(ids);
  } catch (error) {
    console.error('批量删除失败:', error);
  }
};
数据分页

对于大量数据,实现分页查询:

javascript
// 分页获取朋友列表
const getFriendsPaginated = async (page, pageSize) => {
  const totalCount = await db.friends.count();
  const totalPages = Math.ceil(totalCount / pageSize);
  
  const friends = await db.friends
    .offset((page - 1) * pageSize)
    .limit(pageSize)
    .toArray();
  
  return {
    friends,
    pagination: {
      currentPage: page,
      pageSize,
      totalCount,
      totalPages
    }
  };
};
数据缓存策略

结合Vue的响应式系统实现智能数据缓存:

javascript
// composables/useCachedData.js
import { ref, onMounted } from 'vue';
import { db } from '../db';

export function useCachedData(tableName, initialFetch = true) {
  const data = ref([]);
  const lastFetched = ref(null);
  const cacheDuration = 5 * 60 * 1000; // 5分钟缓存
  
  const fetchData = async () => {
    const now = Date.now();
    
    // 如果缓存未过期,直接返回缓存数据
    if (lastFetched.value && (now - lastFetched.value < cacheDuration)) {
      return data.value;
    }
    
    // 否则从数据库获取最新数据
    data.value = await db[tableName].toArray();
    lastFetched.value = now;
    return data.value;
  };
  
  // 强制刷新数据
  const refreshData = async () => {
    lastFetched.value = null;
    return await fetchData();
  };
  
  // 初始加载数据
  if (initialFetch) {
    onMounted(fetchData);
  }
  
  return {
    data,
    fetchData,
    refreshData
  };
}
</script>

常见问题及解决方案

数据库版本升级

当需要修改数据库模式时,需要升级版本:

javascript
db.version(2).stores({
  friends: '++id, name, age, email', // 添加了email字段
  projects: '++id, projectName, status, deadline' // 添加了deadline字段
});
处理数据库连接失败
javascript
try {
  await db.open();
  console.log('数据库连接成功');
} catch (error) {
  console.error('数据库连接失败:', error);
}
数据迁移

当数据库结构变化时,可能需要数据迁移:

javascript
db.version(3).upgrade(trans => {
  return trans.table('friends').toCollection().modify(friend => {
    // 为所有已有朋友记录添加默认邮箱
    if (!friend.email) {
      friend.email = `${friend.name.toLowerCase()}@example.com`;
    }
  });
});