Hi, 👋 ! 如果您发现有什么错误的地方,点这里可以提 issue,🤝🤝
by poplark
最近在做公司某产品第二版的 sdk,使 sdk 提供更具有面向对象的 API。在我做该产品 sdk 的第一版时,发现其中有一些 API 调用很少,很多用户没有使用场景,那么这让我有了在第二版中引入插件机制的想法,sdk 提供最基础的 API,一些不常调用的 API 可以通过插件让用户选择性的加载。虽然实现时碰到一些问题,通过折腾最终倒是找到了一个自认为可行的方案。
具体场景是这样的: ` 假设有 Base, Inherit, Plugin 三个类,Inherit 类继承自 Base 类,Plugin 作为插件类,希望当用户加载插件之后,可以直接通过 Inherit 对象直接调用 Plugin 类中提供的方法。 `
那么就会面临下面几个问题:
在加载插件上,我参考了 Vue 中的 use API Vue.use(PluginObject)
,以及 PluginObject 需要提供 install
方法,只是具体的内部实现上不太一样。对于上面几个问题我这里的实现是:
将 Plugin 原型上的方法直接挂到 Inherit 的原型上,参考代码:
export function applyMixins(inheritCtor: Function, pluginCtor: Function): void {
const keys = Object.getOwnPropertyNames(inheritCtor.prototype);
Object.getOwnPropertyNames(pluginCtor.prototype).forEach((name) => {
if (name === 'constructor') {
// 构造函数,直接跳过
return;
}
if (keys.includes(name)) {
console.warn(`${name} is conflict, will ignore`);
return;
}
inheritCtor.prototype[name] = pluginCtor.prototype[name];
});
}
class Inherit extends Base {
static use(plugin: PluginObject): void {
LocalStream.plugins[plugin.name] = plugin;
applyMixins(LocalStream, plugin.install);
}
}
对于每个 Inherit 对象都需要有一份 Plugin 上的属性的话,我这里采取的是类似于在 Inherit 的构建函数里,调用 Plugin 的构建方法,不过调用时是将 Plugin 的构建方法作为纯函数,并通过 call 改变其运行时的上下文,参考代码:
class Inherit extends Base {
static plugins: PluginObject[] = [];
constructor() {
super();
this.initPlugins();
}
initPlugins(): void {
for (const key in Inherit.plugins) {
const plugin = Inherit.plugins[key];
plugin.install.call(this);
}
}
}
这里可以使用 Interface extends Class,参考代码:
declare module "xxx-lib" {
interface Inherit extends Plugin {}
}
具体理论依据为:TypeScript - Understanding TypeScript 中这样描述 In TypeScript, interfaces can also extend classes, but only in a way that involves inheritance. When an interface extends a class, the interface includes all class members (public and private), but without the class’ implementations.
特别在处理这个问题时,尝试了很多种方法,最终确定是可以这么搞。
最后贴上一段相对完整的示例代码:
主库代码
export interface PluginObject {
name: string;
install: Function;
}
export function applyMixins(derivedCtor: Function, pluginCtor: Function): void {
const keys = Object.getOwnPropertyNames(derivedCtor.prototype);
Object.getOwnPropertyNames(pluginCtor.prototype).forEach((name) => {
if (name === 'constructor') {
// 构造函数,直接跳过
return;
}
if (keys.includes(name)) {
console.warn(`${name} is conflict, will ignore`);
return;
}
derivedCtor.prototype[name] = pluginCtor.prototype[name];
});
}
export class Base {
// ....
}
export class Inherit extends Base {
/**
* @internal
*/
static plugins: {
[key: string]: PluginObject;
} = {};
/**
* 加载插件,使用插件功能
* @param plugin - 插件
*/
static use(plugin: PluginObject): void {
Inherit.plugins[item.name] = plugin;
// 为 Inherit 的补上 Plugin 原型中的方法
applyMixins(Inherit, plugin.install);
});
}
/**
* @internal
*/
initPlugins(): void {
for (const key in Inherit.plugins) {
// 为 Inherit 的对象补上 Plugin 中的成员变量
Inherit.plugins[key].install.call(this);
}
}
constructor() {
super();
this.initPlugins();
}
}
插件库代码
declare module "@my/lib" {
interface Inherit extends Plugin {}
}
export class Plugin {
// ...
}
const plugin: PluginObject = {
name: 'plugin-name',
install: Plugin,
};
export default plugin;