在 Yjs 中,我们可以使用 observe() 和 observeDeep() 来监听协同文档中数据结构的更新。
observe() 和 observeDeep() 的区别如下:
| 方法 | 监听范围 | 典型用途 |
|---|---|---|
observe(cb) | 只监听当前结构(Map / Array) | 精准字段监听 |
observeDeep(cb) | 监听当前结构及其所有子结构 | 嵌套结构复杂时统一处理 |
observe示例
import * as Y from 'yjs'
const doc = new Y.Doc() // 创建一个副本const text = doc.getText('text') // 创建一个文本类型的共享数据
text.observe((event) => { console.log('文本内容发生变化')
event.changes.delta.forEach((change) => { if (change.insert) { console.log('插入的文本内容:', change.insert.toString()) } else if (change.delete) { console.log('删除了', change.delete, '个字符') } else if (change.retain) { console.log('保留了', change.retain, '个字符') } })})
text.insert(0, 'Hello Yjs!') // 插入文本,共享的文本内容发生了变化,因此会触发事件console.log(text.toJSON()) // 打印当前的文本内容text.delete(0, 6) // 删除文本,共享的文本内容发生了变化,因此会触发事件console.log(text.toJSON()) // 打印当前的文本内容event.changes.delta 拿到的是一个数组,该数组描述了共享文本的变化细节:
- insert:新插入的内容
- delete:删除了多少个字符
- retain:跳过多少个字符
课堂练习
请模拟一个“表单”结构,使用 Y.Map 存储字段(如 email、phone),监听字段的变化,并打印具体是哪个字段发生了什么类型的更新(add/update/delete)。
要求:
- 设置初始字段:
email和phone - 修改
phone值 - 删除
email字段 - 用监听输出操作类型(例如:字段
email被删除)
import * as Y from 'yjs'
const doc = new Y.Doc() // 创建一个副本const form = doc.getMap('form') // 创建一个共享的 Map
// 初始化 form 的内容form.set('email', '123@qq.com')form.set('phone', '123456')
// 设置监听操作form.observe((event) => { for (const [key, change] of event.keys.entries()) { const action = change.action const oldValue = change.oldValue const newValue = form.get(key)
if (action === 'delete') { console.log(`Key "${key}" 被删除了. 旧值为: ${oldValue}`) } else if (action === 'add') { console.log(`Key "${key}" 被添加了. 新值为: ${newValue}`) } else if (action === 'update') { console.log( `Key "${key}" 被更新了. 旧值为: ${oldValue}, 新值为: ${newValue}` ) } else { console.log(`Key "${key}" 的操作类型未知.`) } }})
// 接下来修改 form 内容form.set('phone', '654321')form.delete('email')form.set('name', '张三')observeDeep示例
observeDeep 可以监听一整个嵌套的子结构的变化
import * as Y from 'yjs'
const doc = new Y.Doc() // 创建一个副本const root = doc.getMap('root') // 创建一个共享的 Map
root.set('title', new Y.Text('Hello World')) // 设置一个文本
const users = new Y.Array() // 创建一个共享的数组users.push(['小明', '小红']) // 向数组中添加元素root.set('users', users) // 将数组添加到 Map 中
// 接下来进行一个深层次的监听root.observeDeep((event) => { event.forEach((e) => { const path = e.path.join(' > ') || '根对象本身' console.log(`发生变化的位置: ${path}`)
const target = e.target
// 接下来看一下发生变化的共享数据是什么类型 if (target instanceof Y.Text) { console.log('[Text变化]') // 说明是文本发生了变化 e.changes.delta.forEach((change) => { if (change.insert) { console.log(`插入文本: ${change.insert}`) } if (change.delete) { console.log(`删除了 ${change.delete} 个字符`) } if (change.retain) { console.log(`跳过了 ${change.retain} 个字符`) } }) } if (target instanceof Y.Array) { // 说明是数组发生了变化 console.log('[Array变化]') const arrayEvent = e as Y.YArrayEvent<unknown> arrayEvent.changes.delta.forEach((change) => { if (change.insert) { console.log(`插入数组项:`, change.insert) } if (change.delete) { console.log(`删除了 ${change.delete} 个元素`) } if (change.retain) { console.log(`跳过了 ${change.retain} 个元素`) } }) } if (target instanceof Y.Map) { // 说明是 Map 发生了变化 console.log('[Map变化]') const mapEvent = e as Y.YMapEvent<unknown> mapEvent.changes.keys.forEach((change, key) => { console.log(`键 "${key}" 发生了变化,类型是 ${change.action}`) }) } }) // 遍历所有的事件})
const title = root.get('title') as Y.Text // 获取文本
title.insert(4, 'Yjs') // 插入文本
const userList = root.get('users') as Y.Array<string> // 获取数组userList.push(['李四'])e.path :用于描述发生变化的共享数据相对于被监听的根对象的路径,会从你监听的根对象一直到触发事件的具体的数据。
rootMap (Y.Map) ├── title (Y.Text) └── content (Y.Map) ├── section1 (Y.Text) └── metadata (Y.Map) └── author (Y.Text)接下来设置一个深层次的监听
// 注意这里监听的是 rootMap 根对象rootMap.observeDeep((events) => { events.forEach((event) => { console.log('path:', event.path) })})假设现在修改 title rootMap.get('title').insert(0, 'xxx'),这里输出的path 就为 path:title
假设我们修改一个更加深层次的数据 author
rootMap.get('content').get('metadata').get('author').insert('0', 'xxxx')控制台输出的就是 path: content,metadata,author
课堂练习
请创建一个 user 对象(Y.Map),其中包含:
name: 字符串tags:Y.Array,存储标签(如 [“admin”, “editor”])
使用 observeDeep() 监听 user 中任何字段的变化(包括 tags 的 push 操作),打印变更来源(是 name 变了?还是 tags 变了?)
-EOF-