保姆级教程:用Lexical + React + Yjs,从零搭建一个支持多人实时编辑的在线文档(附完整代码)
从零构建实时协作文档:Lexical + React + Yjs 全栈实战指南
想象一下,当你和团队成员同时编辑同一份文档时,每个人的修改都能即时同步,光标位置清晰可见,就像在同一个房间里协作一样流畅。这种实时协作体验已经成为现代生产力工具的标配,而本文将带你从零开始,用Lexical、React和Yjs打造这样一个系统。
1. 环境准备与项目初始化
在开始之前,确保你的开发环境满足以下要求:
- Node.js 16.x或更高版本
- npm 8.x或yarn 1.22.x
- 现代浏览器(Chrome/Firefox/Edge最新版)
创建一个全新的React项目作为我们的起点:
npx create-react-app collaborative-editor --template typescript cd collaborative-editor接下来安装核心依赖:
npm install @lexical/react @lexical/rich-text yjs y-websocket npm install --save-dev @types/yjs @types/y-websocket关键配置要点:
- 使用TypeScript模板以获得更好的类型安全
- 选择y-websocket作为Yjs的WebSocket提供者
- 安装对应的类型定义文件以支持TS开发
2. 构建基础编辑器组件
首先创建一个基础的Lexical编辑器组件EditorCore.tsx:
import { LexicalComposer } from "@lexical/react/LexicalComposer"; import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; import { ContentEditable } from "@lexical/react/LexicalContentEditable"; import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"; import { EditorState } from "lexical"; const editorConfig = { namespace: "CollaborativeEditor", theme: { text: { bold: "font-bold", italic: "italic", underline: "underline", }, }, onError(error: Error) { console.error(error); }, }; export default function EditorCore() { return ( <LexicalComposer initialConfig={editorConfig}> <RichTextPlugin contentEditable={<ContentEditable className="min-h-[200px] p-2 border rounded" />} placeholder={<div className="text-gray-400 absolute top-2 left-2">开始输入...</div>} /> <HistoryPlugin /> <OnChangePlugin onChange={(editorState: EditorState) => { // 后续将在这里处理状态同步 }} /> </LexicalComposer> ); }3. 实现实时协作核心架构
3.1 配置Yjs文档与WebSocket连接
创建collab.ts来管理协作状态:
import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; // 创建共享文档实例 export const ydoc = new Y.Doc(); // 配置WebSocket连接 export const provider = new WebsocketProvider( 'ws://localhost:1234', // WebSocket服务器地址 'collab-demo', // 房间名 ydoc ); // 定义共享数据类型 export const ytext = ydoc.getText('content');3.2 集成协作插件
修改编辑器组件以支持协作:
import { CollaborationPlugin } from "@lexical/yjs"; import { useYjs } from "./collab"; function CollaborativeEditor() { const { provider, ydoc } = useYjs(); return ( <LexicalComposer initialConfig={editorConfig}> <CollaborationPlugin id="main-editor" provider={provider} doc={ydoc} shouldBootstrap={true} /> {/* 其他插件... */} </LexicalComposer> ); }3.3 实现用户状态显示
添加用户光标位置和状态的显示:
import { Cursor } from "@lexical/yjs"; function UserCursors() { const [cursors, setCursors] = useState<Cursor[]>([]); useEffect(() => { const awareness = provider.awareness; const handleAwarenessChange = () => { setCursors(Array.from(awareness.getStates().values())); }; awareness.on('change', handleAwarenessChange); return () => awareness.off('change', handleAwarenessChange); }, [provider]); return ( <div className="absolute top-2 right-2 space-y-1"> {cursors.map((cursor, i) => ( <div key={i} className="flex items-center"> <div className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: cursor.color }} /> <span>{cursor.name || '匿名用户'}</span> </div> ))} </div> ); }4. 搭建后端同步服务
4.1 创建WebSocket服务器
使用Express和ws创建简单的WebSocket服务器:
const express = require('express'); const WebSocket = require('ws'); const { setupWSConnection } = require('y-websocket/bin/utils'); const app = express(); const port = 1234; const server = app.listen(port, () => { console.log(`WebSocket server running on ws://localhost:${port}`); }); const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { setupWSConnection(ws); });4.2 处理冲突解决策略
Yjs使用CRDT(Conflict-Free Replicated Data Type)算法自动解决冲突,但我们仍需要处理一些边界情况:
// 在客户端添加冲突处理逻辑 provider.on('sync', (isSynced: boolean) => { if (isSynced) { console.log('文档同步完成'); // 可以在这里添加同步完成后的处理逻辑 } }); // 处理连接状态变化 provider.on('status', (event: { status: string }) => { console.log('连接状态:', event.status); // 可以根据状态显示不同的UI提示 });5. 部署与性能优化
5.1 生产环境部署配置
# 构建前端 npm run build # 使用PM2管理Node服务 pm2 start server.js --name "collab-server"5.2 性能优化技巧
- 文档分块:对于大型文档,考虑使用Yjs的
Y.Array或Y.Map分块存储 - 节流处理:对高频操作进行适当节流
- 离线支持:使用IndexedDB持久化本地更改
import { IndexeddbPersistence } from 'y-indexeddb'; // 添加离线支持 const persistence = new IndexeddbPersistence('collab-demo', ydoc);6. 高级功能扩展
6.1 实现版本历史记录
import { UndoManager } from 'yjs'; // 创建Undo管理器 const undoManager = new UndoManager(ytext, { captureTimeout: 500, // 操作合并时间窗口 }); // 在UI中添加撤销/重做按钮 function UndoRedoToolbar() { return ( <div className="flex space-x-2"> <button onClick={() => undoManager.undo()}>撤销</button> <button onClick={() => undoManager.redo()}>重做</button> </div> ); }6.2 添加富文本格式支持
扩展基础编辑器以支持更多格式:
import { BoldPlugin, ItalicPlugin, UnderlinePlugin, LinkPlugin } from "@lexical/react/LexicalPlugins"; function RichTextPlugins() { return ( <> <BoldPlugin /> <ItalicPlugin /> <UnderlinePlugin /> <LinkPlugin /> </> ); }在实际项目中,我们遇到的最常见挑战是处理大规模文档的同步延迟问题。通过将文档分块并按需加载,配合适当的UI加载指示器,可以显著提升用户体验。
