前言

最近在做一个新项目,在需求来临之前需要先把技术框架搭建起来,跟我同组的老大哥给我一个代码仓库地址,并让我梳理出来这个仓库的技术框架和实现思路,然后复刻一个。
不就是抄🐴,这还不简单。

装饰器

我大概梳理了这个参考仓库的思路,主要是使用了插件化的思路。而在实现插件化过程中的注册插件时,妙用了装饰器来完成:

  1. 首先使用了单例模式,在一个文件中导出了用于存储插件的变量:
export const registry: Record<string, IPluginConstructor<any>> = {};
  1. 定义了一个向这个变量上挂载插件的函数:
export const regPluginHoc = ({ name }: { name: string }) =>
  (target: any) => {
    if (registry[name] && registry[name].toString() !== target.toString()) {
      logger.warn({
        msg: `插件 ${name} 已存在,请重新命名`,
      });
      return;
    }

    registry[name] = target;
  };

从这个编写方式,你很容易就可以看出来这是一个装饰器函数。
3. 然后我们把这个装饰器用到我们的插件上:

@regPluginHoc({ name: 'init' })
export class InitPlugin extends Plugin<{}> {
  // ……
}

让我们回溯一下,这个装饰器会把这个插件的构造函数挂载到 register 变量中。你必须知道的是,装饰器的运行是在编译时完成的,而不是运行时。因此,理论上在项目启动后,你就可以在项目入口文件中打印 register 变量,并且得到其上挂载的插件构造器对象,然后通过 new 生成一个插件实例。
但实际上,到这里并没有结束。因为即使装饰器是编译时执行的,但也首先要保证,项目在编译时可以索引到这个插件文件,也就是说我们必须在主入口的直接或者间接依赖文件中导入这个插件文件。
4. 导入这个插件文件

import '../plugins/initPlugin';

是的,只需要导入,在编译时可以索引到该文件即可,无需重命名任何实例。
5. 然后在启动后的项目里,可以直接打印 register 这个变量:

console.log(register);

备注

至今为止,装饰器仍然是一个实验性的能力,要让装饰器真正在我们项目中生效不报错,我们需要配置一下 tsconfig.ts 文件,将 experimentalDecorators 属性设置为 true。

{
  "compilerOptions": {
    "experimentalDecorators": true,
  }
}