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