事件系统
在上一节中,我们已经了解了插件的基本结构和注册方式。接下来,我们将深入了解 IPE 的事件系统。
事件系统在 IPE 中扮演着底层的角色,事件系统允许插件之间进行通信和协作。它支持监听运行状态的生命周期事件和提供扩展性的自定义事件。
基本用法
让我们先从一个基本示例开始:
ctx.on('quick-edit/wiki-page', (payload) => {
const { wikiPage } = payload
console.log('当前正在编辑的页面是:', wikiPage.title)
})上述代码片段实现了一个简单的功能:当“快速编辑”插件加载了页面数据后,控制台会输出当前正在编辑的页面标题。
如你所见,ctx.on() 方法监听了一个事件。传入的第一个参数 quick-edit/wiki-page 是事件的名称,而第二个参数则是事件的回调函数。每一次 quick-edit/wiki-page 事件被触发 (即用户进入快速编辑模式,页面数据加载完成) 时都会调用该函数。
事件与会话构成了最基础的交互模型。你在监听器中不仅能够打印控制台消息,还能够像写普通网页脚本一样处理更复杂的事务,例如:
ctx.plugin({
name: 'wiki-editor',
apply(ctx) {
ctx.on('quick-edit/wiki-page', async (payload) => {
const { wikiPage, modal } = payload
const $content = modal.get$content()
if (wikiPage.title.includes('兽耳娘')) {
$content.prepend('好好好!')
}
})
},
})监听事件
聪明的你应该不难发现,这一套 API 非常像 Node.js 的 EventEmitter:第一个参数表示要监听的事件名称,第二个参数表示事件的回调函数。
这套事件系统与 EventEmitter 的一个不同点在于,无论是 ctx.on() 还是 ctx.once() 都会返回一个 dispose 函数,调用这个函数即可取消注册监听器。因此你其实不必使用 ctx.once() 和 ctx.off()。下面给一个只触发一次的监听器的例子:
const dispose = ctx.on('foo', (payload) => {
console.log('只会触发一次')
dispose() // 取消注册
})事件的命名
IPE 的事件名字可以是任意字符串,不过官方插件的事件名称一般遵循 插件ID/事件名称 的命名规范。
我们也推荐这样做,因为这样可以避免事件名称冲突,并且让事件的来源一目了然。
触发事件
如果你开发的插件希望允许其他插件扩展,那么触发事件就是相当合适的手段。
触发事件的基本用法也都与 EventEmitter 类似,第一个参数是事件名称,之后的参数对应回调函数的参数。下面是一个例子:
ctx.emit('custom-event', arg1, arg2, ...rest)
// 对应于
ctx.on('custom-event', (arg1, arg2, ...rest) => {})触发方式
IPE 的事件系统与 EventEmitter 的另一个区别在于,触发一个事件可以有着多种形式,目前支持 4 个不同的方法,足以适应绝大多数需求。
emit: 同时触发所有 event 事件的回调函数parallel: 上述方法对应的异步版本bail: 依次触发所有 event 事件的回调函数;当返回一个false,null,undefined以外的值时将这个值作为结果返回serial: 上述方法对应的异步版本
声明自定义事件
当然,定义事件的类型不是必须的,如果你只是想要开发一些简单的自用插件,完全不必如此大费周章。除非你有强迫症。
对于 TypeScript 开发者来说,你可以通过声明模块的方式,为事件添加类型定义:
interface SomeEventPayload {
foo: string
bar: number
}
declare module '@inpageedit/core' {
interface Events {
'my-plugin/some-event'(payload: SomeEventPayload): void
}
}
ctx.on('my-plugin/some-event', (payload) => {
console.log(payload.foo)
})这样其它开发者即使不去看文档或者读源码,也能通过类型推导,对事件的参数有一个大概的认识。
再比如,你想要监听另一个插件提供的事件,请记得在文件头部导入类型声明:
import type {} from 'inpageedit-plugin-foo' // 引入类型定义
// 如果没有上面的类型导入,下面的代码爆类型错误
ctx.on('foo/someEvent', (payload) => {})