最近在做一个功能重构,比较复杂,有多个模块相互嵌套,因此想用 mixin 模式“拍平”,但又觉得做成插件系统更有利于扩展,于是一直思考两者的差异,想着想着,竟然慢慢模糊了两者的界限。
正好在看 Vue3 源码,看了它的插件概念和实现,豁然开朗,终于搞懂了插件的形式和用途。
首先明确定义:插件是一个 能访问运行时 的完整模块,用来 扩展宿主能力 或者 借助宿主能力实现新的功能,插件一般能拿到完整的宿主对象,所以能使用宿主的所有功能,于是好处显而易见:
我不知道你想实现什么功能,但我把已有的全部功能都给你
因此插件至少需要提供一个钩子函数供宿主调用,这个函数的第一个参数一般是宿主对象,假设这个钩子叫 install()
,那一个简单的插件如下:
const Plugin = {
install(Host, options) {
// 扩展宿主功能
Host.prototype.pluginMethod = function () {}
// 调用宿主功能
Host.someMethod()
}
}
宿主需要有一个 use()
方法来安装插件:
const Host {
use(plugin, options) {
plugin.install(Host, options)
}
}
Host.use(Plugin)
这样的设计下,宿主和插件的耦合非常小,并且插件具有十分灵活的功能。
这就是插件的基本思路,在具体实现上,还有如下3点值得考虑:
- 因为只需要一个钩子函数,所以最简单的插件可以是一个函数;
- 从功能和语义上,插件应该至少有
install()
和uninstall()
两个方法; - 宿主给插件传类还是实例的问题。
对于3,可以看到 Vue2 传的是类,Vue3 传的是实例,两种各有优劣,一般来说,静态功能传类、动态功能传实例比较合适。
弄清这些概念后,就很容易设计一个插件系统了,当然到具体编码时,还有较多细节要处理:比如插件冲突、重复安装、宿主沙盒等等,但这些应该根据具体需求,来决定细致到哪个程度,不建议一上来就陷入这些细节。
功能重构的思路是希望先完成一个核心 MVP,然后横向扩展功能。当时一直以为插件需要有生命周期,不然和 mixin 差别不大,现在才知道生命周期和插件没啥关系。插件是一个依赖宿主的完整功能模块,和 mixin 完全不同。