Cordis 入门 03:用事件让插件通信
插件之间不能总靠直接调用。直接调用会让插件互相知道太多细节,也会让卸载变麻烦。Cordis 提供了事件系统,让插件可以通过事件协作。
这一篇我们练 ctx.on() 和 ctx.emit()。你会看到,事件监听器不是裸挂在全局对象上,而是归属于当前插件的 Fiber。插件卸载时,监听器会一起消失。
这篇要练什么
本文会实现一个最小消息事件:
text
sender plugin -> ctx.emit('demo/message') -> listener plugin你会掌握:
- 用
ctx.on()注册监听器。 - 用
ctx.emit()触发事件。 - 理解监听器为什么会随插件卸载。
- 用事件拆开插件之间的直接依赖。
最小事件例子
把 index.mjs 改成下面这样:
js
import { Context } from 'cordis'
const ctx = new Context()
function listenerPlugin(ctx) {
ctx.on('demo/message', (text) => {
console.log('received:', text)
})
}
await ctx.plugin(listenerPlugin)
ctx.emit('demo/message', 'hello event')运行:
bash
node index.mjs输出:
text
received: hello event这里有两个动作:
ctx.on('demo/message', listener)注册监听器。ctx.emit('demo/message', 'hello event')触发事件。
事件名可以是字符串。入门阶段建议使用带命名空间的事件名,例如 demo/message,这样不容易和别的插件冲突。
验证监听器会被清理
继续改代码:
js
import { Context } from 'cordis'
const ctx = new Context()
function listenerPlugin(ctx) {
ctx.on('demo/message', (text) => {
console.log('received:', text)
})
}
const fiber = ctx.plugin(listenerPlugin)
await fiber
ctx.emit('demo/message', 'before dispose')
await fiber.dispose()
ctx.emit('demo/message', 'after dispose')输出:
text
received: before dispose第二次事件没有输出。原因是插件卸载时,Cordis 自动移除了这个插件注册的监听器。
这就是 Cordis 事件系统和普通事件总线的重要差别。Cordis 的监听器被生命周期接管了。
写一个 sender 插件
现在把触发事件的逻辑也放进插件。
js
import { Context } from 'cordis'
const ctx = new Context()
function listenerPlugin(ctx) {
ctx.on('demo/message', (text) => {
console.log('listener received:', text)
})
}
function senderPlugin(ctx) {
ctx.emit('demo/message', 'message from sender')
}
await ctx.plugin(listenerPlugin)
await ctx.plugin(senderPlugin)输出:
text
listener received: message from sender这里 senderPlugin 不需要知道 listenerPlugin 的存在。它只负责发事件。谁要处理这个事件,由其他插件决定。
这种方式适合这些场景:
- 插件启动后广播一个状态。
- 一个插件产生消息,多个插件分别处理。
- 业务动作需要被日志、指标、通知插件同时观察。
- 插件之间只共享事件协议,不共享实现细节。
小练习:多个监听器
把监听器拆成两个插件:
js
import { Context } from 'cordis'
const ctx = new Context()
function consoleLogger(ctx) {
ctx.on('demo/message', (text) => {
console.log('[console]', text)
})
}
function auditLogger(ctx) {
ctx.on('demo/message', (text) => {
console.log('[audit]', {
event: 'demo/message',
text,
})
})
}
function senderPlugin(ctx) {
ctx.emit('demo/message', 'hello plugins')
}
await ctx.plugin(consoleLogger)
await ctx.plugin(auditLogger)
await ctx.plugin(senderPlugin)输出:
text
[console] hello plugins
[audit] { event: 'demo/message', text: 'hello plugins' }这个例子体现了事件系统的价值:发送方不需要关心有几个接收方。
emit、serial 和 waterfall 怎么选
入门阶段先重点掌握 emit。以后你会遇到更多分发方式。
| API | 适合场景 |
|---|---|
ctx.emit() | 同步广播事件,不等待异步结果 |
ctx.parallel() | 并行执行监听器,并等待全部完成 |
ctx.serial() | 按顺序执行监听器,适合需要顺序控制的流程 |
ctx.bail() | 遇到有效返回值就停止 |
ctx.waterfall() | 洋葱式 next 流程,适合内部拦截链 |
本文先不展开这些高级模式。新手只要先会用 on + emit,就已经能写很多插件通信场景。
入门检查点
事件系统要记住两句话:
text
插件之间可以通过事件通信
事件监听器会跟随当前插件生命周期自动清理下一篇我们进入 Cordis 更有特点的部分:服务和依赖。事件解决的是“发一条消息给谁都行”,服务解决的是“我明确依赖某个能力”。
系列导航:
