告别数据孤岛:Electric与Vue响应式数据同步实战指南
你是否还在为Vue应用中的实时数据同步烦恼?客户端与服务端数据不一致、手动编写大量状态更新代码、复杂的WebSocket连接管理——这些问题不仅消耗开发精力,还会导致用户体验下降。本文将带你一步到位解决这些痛点,通过Electric与Vue的深度集成,实现高效、可靠的响应式数据同步。读完本文,你将掌握如何在Vue项目中无缝集成Electric,构建真正意义上的实时响应式应用。
核心概念与架构解析
Electric工作原理
Electric是一个用于查询数据库的JavaScript库,支持多种数据库,提供灵活的查询构建和结果处理功能。其核心优势在于能够实时同步数据库变更,使前端应用能够即时反映后端数据变化,无需手动刷新或轮询。
Vue响应式系统与Electric集成点
Vue的响应式系统通过ref和reactive函数将数据转换为响应式对象,当数据变化时自动更新相关组件。Electric的订阅机制可以与Vue的响应式系统完美结合,实现数据变更的自动响应:
- 数据订阅:通过Electric订阅数据库表或查询结果的变更
- 响应式包装:将Electric返回的数据包装为Vue响应式对象
- 变更通知:当数据更新时,Electric触发回调,更新响应式对象
- UI更新:Vue自动检测响应式对象变化,更新组件视图
环境准备与安装
系统要求
- Node.js 16.x或更高版本
- Vue 3.x(Composition API)
- npm或yarn包管理器
- PostgreSQL数据库(Electric支持的版本)
安装依赖
# 创建Vue项目(如果尚未创建)
npm create vue@latest electric-vue-demo
cd electric-vue-demo
npm install
# 安装Electric客户端
npm install @electric-sql/client
配置Electric
首先,需要初始化Electric客户端。在项目根目录下创建src/electric.ts文件:
import { ElectricClient } from '@electric-sql/client'
// 初始化Electric客户端
export const electric = new ElectricClient({
url: 'http://localhost:5133', // Electric Sync Service地址
database: 'my-electric-db', // 数据库名称
auth: {
token: 'your-auth-token' // 身份验证令牌(实际项目中应动态获取)
},
// 配置数据同步选项
sync: {
autoStart: true, // 自动启动同步
retry: {
initialDelay: 1000, // 初始重试延迟
maxDelay: 5000 // 最大重试延迟
}
}
})
// 等待客户端连接
export async function initElectric() {
try {
await electric.connect()
console.log('Electric client connected successfully')
return electric
} catch (error) {
console.error('Failed to connect Electric client:', error)
throw error
}
}
核心实现:Vue响应式数据同步
创建Electric-Vue桥接工具
创建src/composables/useElectric.ts文件,实现Electric与Vue的桥接:
import { ref, watch, Ref } from 'vue'
import { electric } from '../electric'
import type { Shape, ShapeChangedCallback } from '@electric-sql/client'
// 将Electric Shape包装为Vue响应式对象
export function useElectricShape<T>(shape: Shape<T>): {
data: Ref<T | null>
loading: Ref<boolean>
error: Ref<Error | null>
unsubscribe: () => void
} {
const data = ref<T | null>(null)
const loading = ref(true)
const error = ref<Error | null>(null)
// 初始加载数据
shape.get().then(initialData => {
data.value = initialData
loading.value = false
}).catch(err => {
error.value = err
loading.value = false
})
// 订阅数据变更
const callback: ShapeChangedCallback<T> = ({ value, error: updateError }) => {
if (updateError) {
error.value = updateError
return
}
data.value = value
}
const unsubscribe = shape.subscribe(callback)
// 组件卸载时取消订阅
watch(
() => {},
() => unsubscribe(),
{ once: true }
)
return { data, loading, error, unsubscribe }
}
// 便捷的表查询组合式函数
export function useElectricTable<T>(tableName: string) {
// 创建表查询Shape
const shape = electric.db[tableName].watch()
// 使用前面定义的hook包装为响应式数据
return useElectricShape<T>(shape)
}
实现响应式数据查询
在Vue组件中使用上述组合式函数,实现响应式数据查询:
<template>
<div class="electric-demo">
<h2>任务列表</h2>
<!-- 加载状态 -->
<div v-if="loading" class="loading">加载中...</div>
<!-- 错误处理 -->
<div v-else-if="error" class="error">
错误: {{ error.message }}
</div>
<!-- 数据展示 -->
<div v-else class="task-list">
<div v-for="task in data" :key="task.id" class="task-item">
<input
type="checkbox"
v-model="task.completed"
@change="handleTaskToggle(task)"
>
<span :class="{ completed: task.completed }">{{ task.title }}</span>
<button @click="handleTaskDelete(task.id)">删除</button>
</div>
</div>
<!-- 添加任务 -->
<div class="add-task">
<input v-model="newTaskTitle" placeholder="输入新任务">
<button @click="handleAddTask">添加</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useElectricTable } from '../composables/useElectric'
import { electric } from '../electric'
// 定义任务类型
interface Task {
id: string
title: string
completed: boolean
created_at: string
}
// 使用Electric查询任务表
const { data, loading, error } = useElectricTable<Task>('tasks')
// 新任务标题
const newTaskTitle = ref('')
// 添加任务
const handleAddTask = async () => {
if (!newTaskTitle.value.trim()) return
try {
await electric.db.tasks.insert({
title: newTaskTitle.value,
completed: false,
created_at: new Date().toISOString()
})
newTaskTitle.value = '' // 清空输入框
} catch (err) {
console.error('添加任务失败:', err)
}
}
// 切换任务完成状态
const handleTaskToggle = async (task: Task) => {
try {
await electric.db.tasks.update({
id: task.id,
completed: !task.completed
})
} catch (err) {
console.error('更新任务失败:', err)
// 回滚UI状态
task.completed = !task.completed
}
}
// 删除任务
const handleTaskDelete = async (taskId: string) => {
try {
await electric.db.tasks.delete({ id: taskId })
} catch (err) {
console.error('删除任务失败:', err)
}
}
</script>
<style scoped>
.task-list {
margin: 20px 0;
}
.task-item {
display: flex;
align-items: center;
margin: 8px 0;
padding: 8px;
border: 1px solid #eee;
border-radius: 4px;
}
.task-item.completed {
text-decoration: line-through;
color: #888;
}
.task-item input {
margin-right: 10px;
}
.task-item button {
margin-left: auto;
background: #ff4444;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
}
.add-task {
margin-top: 20px;
display: flex;
gap: 10px;
}
.add-task input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.add-task button {
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
}
.loading {
color: #666;
padding: 20px;
text-align: center;
}
.error {
color: #dc3545;
padding: 20px;
border: 1px solid #f5c6cb;
border-radius: 4px;
background-color: #f8d7da;
}
</style>
高级功能实现
条件查询与过滤
Electric支持复杂的条件查询,结合Vue的响应式特性,可以实现动态过滤:
// 在组合式函数中添加条件查询支持
export function useFilteredElectricTable<T>(
tableName: string,
filter: Record<string, any> = {}
) {
// 创建带过滤条件的Shape
const shape = electric.db[tableName].watch(filter)
return useElectricShape<T>(shape)
}
// 在组件中使用
const searchQuery = ref('')
const showCompleted = ref(false)
// 响应式过滤条件
const filter = computed(() => {
const conditions: Record<string, any> = {}
// 文本搜索过滤
if (searchQuery.value) {
conditions.title = { contains: searchQuery.value }
}
// 状态过滤
if (showCompleted.value !== undefined) {
conditions.completed = showCompleted.value
}
return conditions
})
// 使用响应式过滤条件
const { data: filteredTasks } = useFilteredElectricTable<Task>('tasks', filter.value)
实时多用户协作
Electric的实时同步特性使得多用户协作变得简单。以下是一个多用户在线状态指示实现:
<template>
<div class="user-presence">
<h3>在线用户</h3>
<div class="user-list">
<div v-for="user in onlineUsers" :key="user.id" class="user-item">
<div class="avatar" :style="{ backgroundColor: user.color }">
{{ user.name.charAt(0) }}
</div>
<span>{{ user.name }}</span>
<div class="status-indicator online"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useElectricTable } from '../composables/useElectric'
// 定义用户类型
interface User {
id: string
name: string
color: string
last_active: string
online: boolean
}
// 订阅用户在线状态表
const { data: onlineUsers } = useElectricTable<User>('user_presence', {
online: true
})
</script>
<style scoped>
/* 样式实现省略 */
</style>
离线数据访问与同步
Electric支持离线数据访问,当网络恢复后自动同步本地变更:
// 增强useElectricShape以支持离线状态
export function useElectricShapeWithOffline<T>(shape: Shape<T>) {
const { data, loading, error, unsubscribe } = useElectricShape<T>(shape)
const isOffline = ref(false)
// 监听连接状态
const connectionUnsubscribe = electric.connectionState.subscribe(state => {
isOffline.value = state.status !== 'connected'
})
// 重写unsubscribe方法,包含连接状态订阅的取消
const enhancedUnsubscribe = () => {
unsubscribe()
connectionUnsubscribe()
}
return { data, loading, error, isOffline, unsubscribe: enhancedUnsubscribe }
}
性能优化策略
数据订阅管理
合理管理数据订阅是优化性能的关键。以下是一些最佳实践:
| 策略 | 适用场景 | 优势 | 潜在风险 |
|---|---|---|---|
| 组件级订阅 | 小型应用、简单数据 | 实现简单,自动清理 | 可能导致重复订阅 |
| 全局状态订阅 | 大型应用、共享数据 | 集中管理,减少重复 | 需要手动管理订阅生命周期 |
| 路由级订阅 | 页面级数据 | 按需加载,路由切换时清理 | 复杂路由场景需额外处理 |
| 按需订阅 | 大型数据集 | 减少初始加载时间 | 用户体验可能受影响 |
批量操作优化
对于大量数据操作,使用批量API可以显著提升性能:
// 批量更新任务状态
async function batchUpdateTasks(taskIds: string[], completed: boolean) {
// 使用事务确保操作原子性
await electric.transaction(async (tx) => {
for (const id of taskIds) {
await tx.tasks.update({ id, completed })
}
})
}
索引优化建议
为常用查询字段创建索引,可以提高Electric的查询性能:
-- 在PostgreSQL中创建索引
CREATE INDEX idx_tasks_title ON tasks(title);
CREATE INDEX idx_tasks_completed ON tasks(completed);
CREATE INDEX idx_tasks_created_at ON tasks(created_at);
常见问题与解决方案
数据同步冲突处理
当多个用户同时修改同一数据时,可能会发生冲突。Electric提供了冲突检测机制:
// 冲突处理示例
const handleTaskToggle = async (task: Task) => {
try {
await electric.db.tasks.update({
id: task.id,
completed: !task.completed,
// 添加乐观锁字段
version: task.version
})
} catch (err) {
if (err instanceof ConflictError) {
// 处理冲突:重新获取最新数据并提示用户
alert('数据已被其他用户修改,请刷新后重试')
// 重新获取数据
shape.refresh()
}
}
}
连接状态管理
实现健壮的连接状态管理,提升用户体验:
<template>
<div class="connection-status" :class="statusClass">
<span class="status-indicator" :class="status"></span>
<span class="status-text">{{ statusText }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { electric } from '../electric'
const status = ref<'connected' | 'connecting' | 'disconnected'>('connecting')
const statusText = ref('连接中...')
const statusClass = ref('connecting')
// 订阅连接状态
const unsubscribe = electric.connectionState.subscribe(state => {
switch (state.status) {
case 'connected':
status.value = 'connected'
statusText.value = '已连接'
statusClass.value = 'connected'
break
case 'connecting':
status.value = 'connecting'
statusText.value = '连接中...'
statusClass.value = 'connecting'
break
case 'disconnected':
status.value = 'disconnected'
statusText.value = '已断开连接'
statusClass.value = 'disconnected'
break
}
})
// 组件卸载时取消订阅
onUnmounted(unsubscribe)
</script>
<style scoped>
/* 样式实现省略 */
</style>
项目实战:构建实时协作任务管理应用
项目结构设计
electric-vue-task-app/
├── src/
│ ├── assets/ # 静态资源
│ ├── components/ # 通用组件
│ │ ├── ConnectionStatus.vue
│ │ ├── TaskItem.vue
│ │ └── UserPresence.vue
│ ├── composables/ # 组合式函数
│ │ ├── useElectric.ts
│ │ └── useTasks.ts
│ ├── electric.ts # Electric配置
│ ├── main.ts # 应用入口
│ ├── router/ # 路由配置
│ ├── views/ # 页面组件
│ │ ├── Dashboard.vue
│ │ ├── TaskDetail.vue
│ │ └── UserProfile.vue
│ └── types/ # 类型定义
├── public/ # 公共资源
└── package.json # 项目依赖
数据库设计
-- 任务表
CREATE TABLE tasks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT NOT NULL,
description TEXT,
completed BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
user_id UUID NOT NULL REFERENCES users(id),
version INTEGER DEFAULT 1
);
-- 用户表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
avatar_url TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- 用户在线状态表
CREATE TABLE user_presence (
user_id UUID PRIMARY KEY REFERENCES users(id),
last_active TIMESTAMP DEFAULT NOW(),
online BOOLEAN DEFAULT false,
color TEXT DEFAULT '#007bff'
);
-- 创建必要的索引
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_completed ON tasks(completed);
核心功能实现
完整实现代码请参考以下文件:
- 任务列表与管理:
views/Dashboard.vue - 任务详情:
views/TaskDetail.vue - 用户资料与设置:
views/UserProfile.vue
部署与监控
部署架构
部署步骤
# 1. 克隆仓库
git clone https://gitcode.com/GitHub_Trending/el/electric.git
cd electric
# 2. 安装依赖
npm install
# 3. 配置环境变量
cp .env.example .env
# 编辑.env文件设置数据库连接等信息
# 4. 构建应用
npm run build
# 5. 启动服务
npm run start
性能监控
Electric提供了内置的性能监控功能:
// 监控查询性能
electric.metrics.subscribe(metrics => {
console.log('查询性能:', metrics.queryPerformance)
console.log('同步延迟:', metrics.syncLatency)
console.log('连接状态:', metrics.connectionState)
})
总结与展望
通过本文介绍的方法,我们实现了Electric与Vue的深度集成,构建了一个功能完善的实时响应式应用。主要收获包括:
- 响应式数据同步:利用Electric的订阅机制和Vue的响应式系统,实现了数据的实时同步
- 离线数据访问:支持离线状态下的数据操作,网络恢复后自动同步
- 多用户协作:通过实时数据同步,实现了多用户协作功能
- 性能优化:学习了多种优化策略,确保应用在大数据量下仍保持良好性能
未来发展方向
- 更深入的Vue集成:开发专门的Vue插件,提供更简洁的API
- 自动代码生成:基于数据库模式自动生成TypeScript类型和API
- 更智能的冲突解决:实现基于业务规则的自动冲突解决
- 性能进一步优化:利用Web Workers处理复杂数据转换,避免主线程阻塞
学习资源推荐
- 官方文档:Electric SQL官方文档提供了详细的API参考和概念解释
- 示例项目:本文配套的示例项目可作为实际开发的参考
- 社区论坛:Electric社区论坛是解决问题和分享经验的好地方
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



