Cordis 入门 05:用插件组合组织功能
前几篇文章里,我们每次都在应用入口安装插件。真实项目里,一个功能常常由多个小插件组成:一个插件提供服务,一个插件监听事件,一个插件启动定时器,一个插件暴露工具。
Cordis 允许插件内部继续安装子插件。这个能力可以把多个小插件组合成一个功能包。
这篇要练什么
本文会实现一个父插件 messageSuite,它内部安装三个子插件:
loggerProvider:提供日志服务。messageListener:监听消息事件。messageTicker:定时发送消息事件。
你会掌握:
- 在插件里调用
ctx.plugin()。 - 理解父插件和子插件的生命周期关系。
- 用组合方式组织功能,而不是把所有代码塞进一个插件。
最小父子插件
先写一个最小父子插件例子。
js
import { Context } from 'cordis'
const ctx = new Context()
function childPlugin(ctx) {
ctx.effect(() => {
console.log('child loaded')
return () => console.log('child disposed')
})
}
function parentPlugin(ctx) {
ctx.plugin(childPlugin)
}
const parentFiber = ctx.plugin(parentPlugin)
await parentFiber
await parentFiber.dispose()输出:
text
child loaded
child disposed父插件安装子插件后,子插件也进入 Cordis 的生命周期管理。父插件卸载时,子插件会被清理。
拆一个消息功能包
现在做一个更像真实项目的组合。
js
import { Context } from 'cordis'
const ctx = new Context()
function loggerProvider(ctx) {
ctx.provide('appLogger', {
info(message) {
console.log(`[info] ${message}`)
},
})
}
const messageListener = Object.assign((ctx) => {
ctx.on('demo/message', (text) => {
ctx.appLogger.info(`message: ${text}`)
})
}, {
inject: ['appLogger'],
})
function messageTicker(ctx) {
ctx.effect(() => {
let count = 1
const timer = setInterval(() => {
ctx.emit('demo/message', `tick ${count++}`)
}, 1000)
return () => clearInterval(timer)
}, 'message ticker')
}
function messageSuite(ctx) {
ctx.plugin(loggerProvider)
ctx.plugin(messageListener)
ctx.plugin(messageTicker)
}
const suiteFiber = ctx.plugin(messageSuite)
await suiteFiber
setTimeout(async () => {
await suiteFiber.dispose()
}, 3100)运行后会看到类似输出:
text
[info] message: tick 1
[info] message: tick 2
[info] message: tick 3这个例子里,入口只安装了 messageSuite。messageSuite 再安装自己的子插件。
为什么要拆成多个插件
你当然可以把所有逻辑都写进一个大插件。
js
function bigPlugin(ctx) {
ctx.provide('appLogger', ...)
ctx.on('demo/message', ...)
ctx.effect(...)
}入门 demo 这样写没问题,但项目变大后会有几个问题:
- 日志服务无法单独复用。
- 事件监听逻辑无法单独测试。
- 定时器和服务注册混在一起,清理边界不清楚。
- 某个子能力出问题时,不容易定位。
拆成多个插件后,每个插件只负责一件事。父插件只负责编排。
| 插件 | 职责 |
|---|---|
loggerProvider | 提供日志服务 |
messageListener | 消费日志服务,监听消息事件 |
messageTicker | 定时发送消息事件 |
messageSuite | 组合前三个插件 |
这就是插件组合的价值:小插件负责能力,大插件负责装配。
小练习:给组合插件加配置
让 messageSuite 支持配置间隔时间。
js
import { Context } from 'cordis'
const ctx = new Context()
function loggerProvider(ctx) {
ctx.provide('appLogger', {
info(message) {
console.log(`[info] ${message}`)
},
})
}
const messageListener = Object.assign((ctx) => {
ctx.on('demo/message', (text) => {
ctx.appLogger.info(`message: ${text}`)
})
}, {
inject: ['appLogger'],
})
function createMessageTicker(interval) {
return function messageTicker(ctx) {
ctx.effect(() => {
let count = 1
const timer = setInterval(() => {
ctx.emit('demo/message', `tick ${count++}`)
}, interval)
return () => clearInterval(timer)
}, 'message ticker')
}
}
function messageSuite(ctx, config) {
ctx.plugin(loggerProvider)
ctx.plugin(messageListener)
ctx.plugin(createMessageTicker(config.interval))
}
const suiteFiber = ctx.plugin(messageSuite, {
interval: 500,
})
await suiteFiber
setTimeout(async () => {
await suiteFiber.dispose()
}, 1600)这个写法用工厂函数创建带配置的子插件。入门阶段这样最直观。更复杂的项目可以把配置拆给各个子插件。
入门检查点
插件组合要记住这句话:
text
父插件负责装配,子插件负责单一能力这样写出来的插件更容易拆分、测试和复用。下一篇我们把前面所有内容合起来,做一个迷你插件运行时:有日志服务、消息存储、定时事件和可卸载的功能包。
系列导航:
