如何设计一个插件系统

280 阅读2分钟

最近在做一个功能重构,比较复杂,有多个模块相互嵌套,因此想用 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点值得考虑:

  1. 因为只需要一个钩子函数,所以最简单的插件可以是一个函数;
  2. 从功能和语义上,插件应该至少有 install()uninstall() 两个方法;
  3. 宿主给插件传类还是实例的问题。

对于3,可以看到 Vue2 传的是类,Vue3 传的是实例,两种各有优劣,一般来说,静态功能传类、动态功能传实例比较合适。

弄清这些概念后,就很容易设计一个插件系统了,当然到具体编码时,还有较多细节要处理:比如插件冲突、重复安装、宿主沙盒等等,但这些应该根据具体需求,来决定细致到哪个程度,不建议一上来就陷入这些细节。

功能重构的思路是希望先完成一个核心 MVP,然后横向扩展功能。当时一直以为插件需要有生命周期,不然和 mixin 差别不大,现在才知道生命周期和插件没啥关系。插件是一个依赖宿主的完整功能模块,和 mixin 完全不同。