当前位置: 首页 > news >正文

Yjs与Vue3的完美结合:手把手教你实现实时协同任务列表

Yjs与Vue3的完美结合:手把手教你实现实时协同任务列表

在当今远程协作成为常态的背景下,实时协同功能已经从"锦上添花"变成了"必不可少"。想象一下,你的团队正在使用一个任务管理工具,每个人都能即时看到其他人的更新,无需刷新页面,没有数据冲突——这就是CRDT技术带来的魔法。本文将带你用Vue3和Yjs构建这样一个系统,从零开始实现一个真正实时的协作式任务列表。

1. 环境准备与项目初始化

首先确保你的开发环境已经安装了Node.js(建议16.x或更高版本)和npm/yarn。我们将使用Vite来快速搭建Vue3项目,这是目前最轻量高效的Vue开发工具链。

npm create vite@latest yjs-vue-todo --template vue-ts cd yjs-vue-todo npm install yjs @syncedstore/core y-webrtc

安装完成后,项目结构应该如下所示:

yjs-vue-todo/ ├── node_modules/ ├── public/ ├── src/ │ ├── assets/ │ ├── components/ │ ├── App.vue │ └── main.ts ├── package.json └── vite.config.ts

我们需要额外创建一个store.ts文件来存放我们的协同状态逻辑:

// src/store.ts import { syncedStore, getYjsDoc } from "@syncedstore/core"; import { WebrtcProvider } from "y-webrtc"; type TodoItem = { title: string; completed: boolean; createdAt: number; }; export const store = syncedStore({ todos: [] as TodoItem[], room: "default-room" }); const doc = getYjsDoc(store); export const webrtcProvider = new WebrtcProvider("yjs-vue-todo-room", doc);

2. 核心协同逻辑实现

Yjs的核心在于它的CRDT(Conflict-Free Replicated Data Type)算法。简单来说,这种数据结构允许多个用户同时编辑同一份数据,而无需担心冲突问题。当网络延迟或离线编辑发生时,所有更改最终会收敛到一致状态。

让我们深入看看如何将这一机制应用到我们的任务列表中:

2.1 状态管理与Vue集成

SyncedStore是Yjs的一个上层抽象,它让我们可以用更直观的方式操作共享状态。在Vue中启用绑定支持:

// src/main.ts import { createApp } from 'vue' import App from './App.vue' import { enableVueBindings } from '@syncedstore/core'; import * as Vue from 'vue'; enableVueBindings(Vue); createApp(App).mount('#app')

现在,我们可以在组件中直接使用这个共享store:

<!-- src/App.vue --> <script setup lang="ts"> import { store } from './store' import { ref } from 'vue' const newTodo = ref('') const addTodo = () => { if (!newTodo.value.trim()) return store.todos.push({ title: newTodo.value.trim(), completed: false, createdAt: Date.now() }) newTodo.value = '' } const toggleTodo = (index: number) => { store.todos[index].completed = !store.todos[index].completed } const removeTodo = (index: number) => { store.todos.splice(index, 1) } </script>

2.2 WebRTC对等连接配置

Y-webrtc使用WebRTC技术建立点对点连接,这意味着数据可以直接在客户端之间传输,而不必经过中央服务器。这既提高了速度,又增强了隐私性。

// 在store.ts中扩展配置 export const webrtcProvider = new WebrtcProvider("yjs-vue-todo-room", doc, { signaling: ['wss://signaling.yjs.dev'], password: 'optional-room-password', awareness: { // 可以添加用户状态信息 user: { name: 'Anonymous', color: '#000000' } } })

提示:在生产环境中,你可能需要自建信令服务器以获得更好的稳定性。Yjs官方提供了信令服务器的Docker镜像。

3. 构建任务列表UI

现在让我们创建一个功能完整且美观的任务列表界面。我们将使用Vue的组合式API和TypeScript来确保类型安全。

<template> <div class="todo-app"> <h1>协同任务列表</h1> <div class="input-area"> <input v-model="newTodo" @keyup.enter="addTodo" placeholder="输入新任务..." /> <button @click="addTodo">添加</button> </div> <div class="filter-controls"> <button :class="{ active: filter === 'all' }" @click="filter = 'all'" >全部</button> <button :class="{ active: filter === 'active' }" @click="filter = 'active'" >未完成</button> <button :class="{ active: filter === 'completed' }" @click="filter = 'completed'" >已完成</button> </div> <ul class="todo-list"> <li v-for="(todo, index) in filteredTodos" :key="todo.createdAt" :class="{ completed: todo.completed }" > <input type="checkbox" :checked="todo.completed" @change="toggleTodo(index)" /> <span>{{ todo.title }}</span> <button @click="removeTodo(index)">删除</button> </li> </ul> <div class="stats"> <span>{{ activeCount }} 项待完成</span> <button v-if="completedCount > 0" @click="clearCompleted" >清除已完成</button> </div> </div> </template> <script setup lang="ts"> import { store } from './store' import { ref, computed } from 'vue' const newTodo = ref('') const filter = ref<'all' | 'active' | 'completed'>('all') const filteredTodos = computed(() => { switch (filter.value) { case 'active': return store.todos.filter(t => !t.completed) case 'completed': return store.todos.filter(t => t.completed) default: return [...store.todos].sort((a, b) => a.createdAt - b.createdAt) } }) const activeCount = computed(() => store.todos.filter(t => !t.completed).length) const completedCount = computed(() => store.todos.filter(t => t.completed).length) const clearCompleted = () => { const completedIndexes = store.todos .map((t, i) => t.completed ? i : -1) .filter(i => i !== -1) .reverse() completedIndexes.forEach(i => store.todos.splice(i, 1)) } </script> <style scoped> /* 添加适当的样式 */ .todo-app { max-width: 600px; margin: 0 auto; padding: 20px; } .todo-list { list-style: none; padding: 0; } .todo-list li { display: flex; align-items: center; padding: 10px; border-bottom: 1px solid #eee; } .todo-list li.completed span { text-decoration: line-through; color: #999; } .input-area { display: flex; margin-bottom: 20px; } .input-area input { flex-grow: 1; padding: 8px; margin-right: 10px; } .filter-controls { margin-bottom: 20px; } .filter-controls button { margin-right: 5px; background: none; border: 1px solid #ddd; padding: 5px 10px; cursor: pointer; } .filter-controls button.active { background: #eee; } .stats { margin-top: 20px; color: #666; font-size: 0.9em; display: flex; justify-content: space-between; } </style>

4. 高级功能与优化

一个真正的生产级协同应用需要考虑更多细节。让我们添加一些增强功能:

4.1 用户状态感知

Yjs的Awareness API可以让我们知道谁正在编辑文档:

// 在store.ts中添加 export const awareness = webrtcProvider.awareness awareness.setLocalStateField('user', { name: prompt('请输入你的名字') || '匿名用户', color: `#${Math.floor(Math.random()*16777215).toString(16)}` })

然后在组件中显示在线用户:

<template> <div class="online-users"> <h3>在线用户 ({{ onlineUsers.length }})</h3> <div v-for="user in onlineUsers" :key="user.id" class="user-badge" :style="{ backgroundColor: user.state.user.color }" > {{ user.state.user.name }} </div> </div> </template> <script setup lang="ts"> import { awareness } from './store' import { ref, onMounted, onUnmounted } from 'vue' const onlineUsers = ref<{id: number, state: any}[]>([]) const updateUsers = () => { onlineUsers.value = Array.from(awareness.getStates().entries()) .map(([id, state]) => ({ id, state })) } onMounted(() => { awareness.on('change', updateUsers) updateUsers() }) onUnmounted(() => { awareness.off('change', updateUsers) }) </script> <style scoped> .online-users { margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; } .user-badge { display: inline-block; padding: 3px 8px; margin: 0 5px 5px 0; border-radius: 3px; color: white; font-size: 0.8em; } </style>

4.2 离线支持与数据持久化

Yjs天生支持离线编辑,但我们需要添加本地持久化来防止数据丢失:

// 在store.ts中添加 import * as Y from 'yjs' import { IndexeddbPersistence } from 'y-indexeddb' // 添加IndexedDB持久化 export const persistence = new IndexeddbPersistence('yjs-vue-todo', doc) persistence.whenSynced.then(() => { console.log('数据已从本地存储加载') })

4.3 冲突解决与历史记录

Yjs内置了撤销/重做功能,我们可以轻松实现:

// 在store.ts中添加 import { UndoManager } from 'yjs' // 为todos数组创建撤销管理器 export const undoManager = new UndoManager(doc.getArray('todos'), { captureTimeout: 500 // 将500ms内的操作合并为一个撤销步骤 })

然后在组件中添加对应的控制按钮:

<template> <div class="undo-controls"> <button @click="undo" :disabled="!canUndo">撤销</button> <button @click="redo" :disabled="!canRedo">重做</button> </div> </template> <script setup lang="ts"> import { undoManager } from './store' import { ref, onMounted, onUnmounted } from 'vue' const canUndo = ref(false) const canRedo = ref(false) const updateButtons = () => { canUndo.value = undoManager.undoStack.length > 0 canRedo.value = undoManager.redoStack.length > 0 } const undo = () => { undoManager.undo() updateButtons() } const redo = () => { undoManager.redo() updateButtons() } onMounted(() => { undoManager.on('stack-item-added', updateButtons) undoManager.on('stack-item-popped', updateButtons) updateButtons() }) onUnmounted(() => { undoManager.off('stack-item-added', updateButtons) undoManager.off('stack-item-popped', updateButtons) }) </script>

5. 部署与性能优化

当应用准备上线时,我们需要考虑一些性能和安全方面的优化:

5.1 生产环境配置

// 修改WebRTC提供者配置 export const webrtcProvider = new WebrtcProvider(process.env.VITE_ROOM_NAME || 'default-room', doc, { signaling: process.env.VITE_SIGNALING_SERVER ? [process.env.VITE_SIGNALING_SERVER] : ['wss://signaling.yjs.dev'], password: process.env.VITE_ROOM_PASSWORD, maxConns: 20, // 限制每个客户端的连接数 filterBcConns: true // 过滤掉浏览器扩展的连接 })

5.2 性能监控

添加性能监控可以帮助我们发现潜在问题:

// 监控文档更新频率 setInterval(() => { const stats = { updateCount: doc.store.updates.length, gcSize: doc.gcSize, memoryUsed: doc.memoryUsed } console.log('文档统计:', stats) }, 10000)

5.3 安全性增强

对于敏感应用,可以考虑添加额外的安全层:

// 加密文档内容 import { encrypt, decrypt } from 'yjs-encryption' const encryptionKey = 'your-secret-key' // 在发送更新前加密 provider.on('update', update => { const encrypted = encrypt(update, encryptionKey) // 发送加密后的更新 }) // 接收更新时解密 provider.on('message', message => { const decrypted = decrypt(message, encryptionKey) Y.applyUpdate(doc, decrypted) })
http://www.cnnetsun.cn/news/1623214.html

相关文章:

  • Excel折线图 vs 散点图:用两列数据做图表,90%人选错了类型?
  • 圆钢棒料剪切机的设计【设计说明书+CAD图纸+SW三维+STEP通用格式】 钢筋截断设备
  • 靶场合集|漏洞挖掘从入门到封神:新手 / 进阶 / 高阶 + 搭建使用全指南
  • 3大播放痛点?MPV_lazy播放器深度解密:从零配置到极致性能实战指南
  • STM32毕设选题避坑指南:从“智能衣柜”到“宠物投喂”,学长教你如何选一个不后悔的题目
  • 不用写代码!用Langflow可视化编排学术研究Agent的保姆级教程
  • Notepad--:轻量高效的跨平台文本编辑解决方案
  • Halcon实战:5步搞定液压工程中的粒子运动跟踪(附完整代码)
  • 保姆级教程:用Python脚本把BDD100K数据集转成YOLOv5/v8能用的格式(附完整代码)
  • Transformer在异常检测里‘卷’出新高度:深入拆解Anomaly Transformer的Min-Max训练与Sigma参数调优
  • CBAM实战指南:如何通过通道与空间注意力提升CNN模型性能
  • mPLUG高清图文分析作品集:从街景识别到艺术画作描述的多样化输出
  • 如何彻底优化Windows 11性能:Win11Debloat系统清理终极指南
  • VMWare共享文件夹终极指南:Ubuntu20.04.6与Windows文件互传详解
  • FileZilla与WSL2的SSH文件传输:跨系统高效协作指南
  • 403 Forbidden错误深度排查:Phi-3-mini智能诊断方案
  • 华为云OBS临时URL进阶:巧用响应头重写实现下载文件自定义命名
  • 突破QQ音乐格式限制的创新方案:qmcflac2mp3音频自由转换工具
  • 文件批量提取与过滤工具在运维场景中的应用
  • 从6k到25k!零基础女生4个月逆袭成AI算法工程师,她的故事告诉你学历不重要,方法才关键!
  • Qwen-Image-2512-Pixel-Art-LoRA 错误排查手册:常见生成问题与解决方案汇总
  • 掌握15兆瓦海上风力涡轮机核心技术:IEA-15-240-RWT项目完全指南
  • 终极Pwndbg调试器配置指南:从新手到专家的5个关键步骤
  • 突破窗口尺寸限制:WindowResizer重新定义桌面空间掌控力
  • 03 MongoDB文档的各种增加、更新、删除操作总结
  • 革新性Windows系统管理工具:一站式效能优化与维护解决方案
  • GDScript快速上手:3天从零基础到游戏开发的完整指南
  • 高效人脸检测:从模型选择到边缘部署全攻略
  • 收藏!小白程序员轻松入门大模型:从工具到产品的完整能力流构建指南
  • 西圣FindX、蜂鸟3Plus哪款好?可视掏耳勺哪个好?实测对比