ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 概述 Yjs 是一个用于构建实时协作应用的高效、基于 JSON 的共享编辑框架。它允许多个用户在多个设备上同时编辑同一个文档,具有强大的同步功能。Yjs 的核心功能包括**分布式同步**、**冲突处理**、**离线编辑**、以及**性能优化**,使其成为现代协作应用程序开发的热门选择。 ### 主要特点: 1. **冲突自由复制数据类型 (CRDT)**:Yjs 使用 CRDT 数据结构,确保在多个用户编辑同一数据时,无需中央服务器管理,系统可以自动解决冲突。 2. **离线支持**:用户可以在离线状态下编辑数据,Yjs 会在用户重新连接时自动同步修改。 3. **高效同步**:Yjs 的架构设计使其可以在传输非常少的数据的情况下,确保文档的完整同步,适合低带宽和高延迟的环境。 4. **可扩展性**:Yjs 可以与各种不同的传输层(如 WebSocket、WebRTC)和数据库(如 IndexedDB、Redis)结合,适用于不同的使用场景。 5. **集成与插件支持**:Yjs 通过插件支持与多种流行的编辑器(如 ProseMirror、CodeMirror 和 Quill)集成,用于处理富文本编辑、代码协作等需求。 ### 适用场景: * **实时文档编辑**:类似于 Google Docs 的多人实时编辑应用。 * **协作设计工具**:允许多个用户同时编辑图形或 UI 的工具。 * **多人在线笔记**:像 Notion、Evernote 这样的工具,可以实现跨设备的内容同步。 ## 语法 ### Y.doc 代表一个文档对象,存储共享数据并支持实时协作。它是基于 CRDT(冲突自由的复制数据类型)实现的,允许多个客户端在没有中央服务器的情况下进行并发操作,并且自动解决数据冲突 **主要功能:** 1. **管理文档的状态**:`Y.Doc` 包含所有实时共享的数据结构,如数组、映射、文本等。开发者可以通过它创建和操作共享的数据。 2. **同步功能**:`Y.Doc` 支持离线和在线的协作,当用户重新连接网络时,`Y.Doc` 会自动将本地更改与其他用户同步。 3. **模块化与扩展**:它可以通过多种通信协议(WebSocket、WebRTC 等)进行同步,并与数据库如 IndexedDB、Redis 结合存储和同步状态。 示例 <details> <summary> Y.doc 示例 </summary> ``` import * as Y from 'yjs' const doc1 = new Y.Doc(); const doc2 = new Y.Doc(); doc1.on('update', (update) => { console.log('doc1 update', ) Y.applyUpdate(doc2, update); }); doc2.on('update', (update) => { console.log('doc2 update', ) Y.applyUpdate(doc1, update); }); doc1.getArray('myarray').insert(0, ['Hello doc2, you got this?']); const res = doc2.getArray('myarray').get(0); console.log(res.toString()) // output // doc1 update // doc2 update // Hello doc2, you got this? ``` </details> ### Shared Types 核心数据类型 * [Y.Map](https://docs.yjs.dev/api/shared-types/y.map) * [Y.Array](https://docs.yjs.dev/api/shared-types/y.array) * [Y.Text](https://docs.yjs.dev/api/shared-types/y.text) * [Y.XmlFragment](https://docs.yjs.dev/api/shared-types/y.xmlfragment) * [Y.XmlElement](https://docs.yjs.dev/api/shared-types/y.xmlelement) * [Y.XmlText](https://docs.yjs.dev/api/shared-types/y.xmltext) #### Y.Text <details> <summary>Y.Text 示例</summary> ``` import * as Y from 'yjs' // 创建一个新的 Yjs 文档 const ydoc = new Y.Doc() // 从文档中获取一个 Y.Text 实例 const ytext = ydoc.getText('my-shared-text') // 监听文本变化 ytext.observe(event => { console.log('文本变化:', event.changes.delta) }) // 向文本中插入内容 ytext.insert(0, 'Hello, ') // 在特定位置插入内容 ytext.insert(7, 'Yjs ') // 删除内容 ytext.delete(0, 5) // 替换内容 ytext.delete(0, 2) ytext.insert(0, 'Greetings') // 获取当前文本内容 console.log('当前文本:', ytext.toString()) // 获取文本长度 console.log('文本长度:', ytext.length) // 在文本的末尾追加内容 ytext.insert(ytext.length, '!') // 使用 insert 方法一次插入多个字符 ytext.insert(ytext.length, ' How are you?') console.log('当前文本:', ytext.toString()) // output // 文本变化: [ { insert: 'Hello, ' } ] // 文本变化: [ { retain: 7 }, { insert: 'Yjs ' } ] // 文本变化: [ { delete: 5 } ] // 文本变化: [ { delete: 2 } ] // 文本变化: [ { insert: 'Greetings' } ] // 当前文本: GreetingsYjs // 文本长度: 13 // 文本变化: [ { retain: 13 }, { insert: '!' } ] // 文本变化: [ { retain: 14 }, { insert: ' How are you?' } ] // 当前文本: GreetingsYjs ! How are you? ``` </details> ### Y.XmlElement 使用用在 html 中 <details> <summary> Y.XmlElement 示例</summary> ``` import * as Y from 'yjs' const ydoc = new Y.Doc() const xml = ydoc.get("prop-name", Y.XmlElement) const paragraph = new Y.XmlElement('p') paragraph.setAttribute('class', 'important') paragraph.insert(0, [new Y.XmlText('Hello, world!')]) xml.insert(0, [paragraph]) console.log(paragraph.toString()) console.log(xml.toString()) // output // <p class="important">Hello, world!</p> // <undefined><p class="important">Hello, world!</p></undefined> ``` </details> ### Y.UndoManager 撤回 <details> <summary>撤回示例</summary> ``` import * as Y from 'yjs' const ydoc = new Y.Doc() const ytext = ydoc.getText('text') const undoManager = new Y.UndoManager(ytext) ytext.insert(0, 'abc') undoManager.undo() console.log(ytext.toString()) // => '' undoManager.redo() console.log(ytext.toString()) // => 'abc' ``` </details> ### Delta Format 可通过 Delta 把数据通过到另一个 doc 中 <details> <summary>ytext.applyDelta 同步到另一个 Y.doc</summary> ``` import * as Y from 'yjs' const ydoc = new Y.Doc() const ytext = ydoc.getText('demo') ytext.insert(0, 'abc') ytext.insert(1, '123', { bold: true ,class:'desc'}) const delta = ytext.toDelta() // 通过 delta 转到 doc2 中 const doc2 = new Y.Doc() const ytext2 = doc2.getText('demo') ytext2.applyDelta(delta) console.log(ytext2.toDelta()) // 输出 // [ // { insert: 'a' }, // { insert: '123', attributes: { bold: true, class: 'desc' } }, // { insert: 'bc' } // ] ``` </details> ### Subdocuments 可进行 doc 的嵌套 ``` import * as Y from 'yjs' // Client One const rootDoc = new Y.Doc() const folder = rootDoc.getMap() const subDoc = new Y.Doc() subDoc.getText().insert(0, 'some initial content') folder.set('my-document.txt', subDoc) // Client Two const subDoc2 = rootDoc.getMap().get('my-document.txt') const subDocText2 = subDoc2.getText() console.log(subDocText2.toString()) // => "" - content is empty // Data needs to be loaded first.. subDoc2.load() // Then the providers will fetch data from the database / network // and eventually fill the content subDoc2.on('synced', () => { console.log(subDocText2.toString()) // => "some initial content" }) // It is hard to determine when data was actually synced. // The synced event is still helpful to show the user that this user synced // with other users. // It is safer to observe the data types and don't listen // to an explicit sync event. subDocText2.observe((data) => { console.log('data changed..', data) }) ```