你用过的插件
你知道Vue
是一个负责页面生成和数据交互JS框架,通过vue.use()
的方式可以轻松给vue
添加各种功能,例如:你可以通过加入vue-router
来增强路由能力、或者添加pinia
来支持数据共享等。
在 Vue
的 main.js
文件中,你会发现像 vue-router
、vue-i18n
等第三方库是如何被集成的。
在Vite
的vite.config.js
中,你会发现像下面这样的配置。
在Webpack
的webpack.config.js
中,你会发现像下面这样的配置。
当然如果你了解Pictode
那么,也会在Pictode
中发现类似的用法。
什么是插件?
想象你在写一个软件,比如一个文本编辑器。你的编辑器很基础,只有最基本的文本输入和保存功能。但是你知道,有很多用户想要更高级的功能,比如拼写检查、代码高亮显示或者版本控制。为了让你的编辑器不至于变得臃肿,你可以使用插件系统。
插件就像是一组小工具箱,每个工具箱都包含一个特定的功能。当用户需要某个功能时,他们可以选择安装相应的插件,就像你在工具箱里挑选需要的工具一样。这样,用户可以根据自己的需求定制编辑器,而不必把所有可能用到的功能都塞到一个庞大的软件里。
在程序员的角度,插件就是一种模块化的设计方式,让你的软件更容易扩展和维护。你可以定义一些接口(API),让插件开发者知道如何与你的软件进行交互。这样一来,其他程序员就可以编写插件,扩展你的软件的功能,而不必深入了解你软件的全部实现细节。
看到这里你肯定也想知道如何给自己的系统增加插件机制吧!
插件约定
插件的核心在于提供给开发者的一组规范化接口,为软件与插件之间提供有效交互。确立这组接口的内容是实现插件的首要步骤。
需要明确的是,这组插件接口并非供外部直接调用,而是为系统与插件之间的内部交互而设计。
以 Pictode
的插件接口为例,我们进行如下规范制定:
ts
export interface Plugin {
name: string; // 插件名称,也是插件的唯一标识
install(app: App, ...options: any[]): any; // 插件安装时调用
destroy(): void; // 插件销毁时调用
enable?(): void; // 插件启用时调用
disable?(): void; // 插件禁用时调用
isEnabled?(): boolean; // 检查插件是否启用
}
主应用插件管理
插件安装
我们使用Map来管理注册的插件,因为所有的插件都有一个唯一的标识:name
。
ts
import { Plugin } from './types';
export class App {
private installedPlugins: Map<string, Plugin> = new Map();
}
在接口约定时提到过,这些方法并不是供外部调用的,而是为主系统调用的。通过 app.use(plugin)
安装插件,插件的安装逻辑包括将插件记录到 Map
中,并调用我们约定的插件方法 install()
。这一设计使得插件的调用和管理更加清晰,同时确保了内部接口的安全性。
ts
import { Plugin } from './types';
export class App {
private installedPlugins: Map<string, Plugin> = new Map();
public use(plugin: Plugin, ...options: any[]): this {
if (!this.installedPlugins.has(plugin.name)) {
this.installedPlugins.set(plugin.name, plugin);
plugin.install(this, ...options);
}
return this;
}
}
主应用获取插件
前面我们说过通过Map
管理插件,所以获取插件的也会极其简单。
ts
export class App {
private installedPlugins: Map<string, Plugin> = new Map();
// ...
public getPlugin<T extends Plugin>(pluginName: string): T | undefined {
return this.installedPlugins.get(pluginName) as T;
}
public getPlugins<T extends Plugin[]>(pluginNames: string[]): T | undefined {
return pluginNames.map((pluginName) => this.getPlugin(pluginName)) as T;
}
}
控制应用的启用与禁用
应用的启用和禁用核心逻辑仍然通过调用插件的 enable()
和 disable()
方法来实现。这种设计使得应用状态的管理更为直观和可控。
ts
export class App {
public enablePlugin(plugins: string | string[]): this {
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
const aboutToChangePlugins = this.getPlugins(plugins);
aboutToChangePlugins?.forEach((plugin) => plugin?.enable?.());
return this;
}
public disablePlugin(plugins: string | string[]): this {
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
const aboutToChangePlugins = this.getPlugins(plugins);
aboutToChangePlugins?.forEach((plugin) => plugin?.disable?.());
return this;
}
}
插件卸载
插件卸载的核心逻辑包括从 Map
中移除插件,并在移除之前调用插件的 destroy()
方法。这一流程确保了插件的清理和资源释放。
ts
import { Plugin } from './types';
export class App {
private installedPlugins: Map<string, Plugin> = new Map();
public destroyPlugins(plugins: string | string[]): this {
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
const aboutToChangePlugins = this.getPlugins(plugins);
aboutToChangePlugins?.forEach((plugin) => plugin?.destroy());
return this;
}
}
插件实现
这里给出一个示例插件,然后再将该插件注册到主应用。
ts
class TestPlugin implements Plugin {
public name: string = "TestPlugin";
private _enable: boolean = false;
public install(...options: any[]) {
console.log(`${this.name}安装了`);
this._enable = true;
}
public disable(): void {
console.log(`${this.name}被禁用了`);
this._enable = false;
}
public enable(): void {
console.log(`${this.name}启用了`);
this._enable = true;
}
public destroy(): void {
console.log(`${this.name}要被卸载了`);
}
public isEnabled(): boolean {
console.log(`${this.name}目前时是${this._enable}`);
return this._enable;
}
// 插件的功能方法
public testFunc(): void {
console.log(`${this.name}的功能方法`);
}
}
你写完上面的代码基本上实现了插件的核心逻辑,插件方法的调用方式如下:
ts
const app = new App();
const testPlugin = new TestPlugin();
app.use(testPlugin);
插件的功能方法调用:
ts
app.getPlugin("TestPlugin")?.testFunc();
🎉🎉🎉恭喜你喜提一个插件系统,你的应用可以轻松接收其他开发者的贡献了。
更便捷的使用方式
上述插件功能的调用方式是首先获取插件实例,然后通过实例调用插件方法。虽然这是一种合理的调用方式,但并不是最便捷的使用方法。
为提升使用体验,理想的调用方式应该是在插件安装后,可以直接通过 app
调用插件提供的功能方法。这样,开发者可以更轻松、直观地使用插件功能,提高了整体的可用性。
好了,现在是时候规划一下项目的目录情况了,为了提高代码的可读性我们规划代码目录如下:
我们将插件专门分配到一个名为 plugin-test
的目录,这个目录将包含插件的所有内容。
index.ts
:包含插件的主要实现代码。types.ts
:用于定义插件中使用的类型数据。methods.ts
:是一个文件,将插件的功能方法挂载到主应用。
在types.ts
中给App
声明插件的功能函数
ts
import { App } from "../app";
declare module "../app" {
export interface App {
testFunc(): void;
}
}
在methods.ts
中就可以自然而然的挂载testFunc
方法到App
的原型了
ts
import { App } from "../app";
import { TestPlugin } from "./index";
App.prototype.testFunc = function () {
const testPlugin = this.getPlugin("TestPlugin");
if (!testPlugin) {
return;
}
(testPlugin as TestPlugin).testFunc();
};
这时再从主应用使用插件功能方法时就会得到提示:
最后
🎉🎉🎉 恭喜,你成功地搭建了一个可用的插件系统。通过上述方法,你轻松实现了系统的扩展,而且无需修改主要逻辑即可实现这一扩展。这个插件系统为你的应用带来了更大的灵活性和可维护性。希望这个系统为你的项目带来更多的可能性和便利。
如果你觉得插件机制
对你在开发中有所帮助,麻烦多点赞评论收藏😊
如果插件机制
对你实现某些业务有所启发,麻烦多点赞评论收藏😊
如果...,麻烦多点赞评论收藏😊
如果大家有其他弹窗方案,欢迎留言交流哦!