Skip to content

监听机制

在 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 拿到的是一个数组,该数组描述了共享文本的变化细节:

课堂练习

请模拟一个“表单”结构,使用 Y.Map 存储字段(如 emailphone),监听字段的变化,并打印具体是哪个字段发生了什么类型的更新(add/update/delete)。

要求:

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),其中包含:

使用 observeDeep() 监听 user 中任何字段的变化(包括 tags 的 push 操作),打印变更来源(是 name 变了?还是 tags 变了?)


-EOF-