Typescript装饰器-将方法装饰器链接到类装饰器

时间:2019-03-30 22:50:03

标签: typescript decorator

我正在开发一个图书馆,该图书馆将实现一些自定义的Web请求路由设置。而且我希望能够使用像这样的typecirpt装饰器来实现此功能。

@Controller({ path: '/api' })
class TestController {
  @Route('get', '/')
  get() {
    return 'banana';
  }
}

我遇到的问题是,我似乎无法将'child'方法修饰器链接到'parent'类修饰器。

我有一些非常简单的装饰器工厂,您可以在这里看到:

export function Controller(params?: IControllerParams) {
  const func: ClassDecorator = (target) => {
    registerController(target, params || {});
    logger.info(`Registered controller: ${target.name}`);
    console.dir(target); // [Function: TestController]
  };

  return func;
}

export function Route(verb: Verb, path: string) {
  const func: MethodDecorator = (target, key) => {
    registerRoute(verb, path, key, target);
    logger.info(`Registered route: ${path} for verb: ${verb}`);
    console.dir(target); // TestController {}
  };

  return func;
}

现在的问题是,每个装饰器实例返回的目标类型都有些许不同,这意味着我无法对其进行比较。 类方法为我的类返回一个Function签名,而该方法返回一个命名对象签名。

有什么我想念的东西吗?我看过其他图书馆也可以进行这种链接,所以我知道应该可以!

2 个答案:

答案 0 :(得分:1)

当然,发布后不久,我会处理您的问题。我只需要将“父”类目标的原型与“子”方法目标进行比较,它们就会匹配。

答案 1 :(得分:1)

我以前实际上遇到过这个确切的问题,并且有很多复杂的问题。

首先,可以,您很容易失去对“ this”值的访问权限,因此必须小心。另一种方法是将每个函数视为恰好在对象中定义的静态纯方法。第二个是装饰器的评估顺序,就像您可能已经知道的那样,它由内而外。

记住这两个,这就是我所做的。我在Meteor上使用了此代码,它的功能与您正在做的非常相似。

序言

ServerModule只是一个带有一系列方法处理程序的类。又称为控制器,此代码是为Meteor构建的。

代码

/**
 * This is horribly ugly code that I hate reading myself, 
 * but it is very straightforward. It defines a getter
 * property called __modulle, and returns the data that
 * we care about in a format that is readable for a future
 * registry/bootstrapping system
 */
function boltModuleProperty(proto: any) {
  Object.defineProperty(proto, '__module', {
    get: function () {
      let obj: IModuleDetails = {};
      for (let key in this.__moduleFunctions)
        obj[`${this.__moduleName}.${key}`] = this.__moduleFunctions[key];
      return obj;
    }
  })
}

/**
 * This is evaluated at the very end.
 * 
 * Collect all the methods and publications, registering
 * them with Meteor so they become available via the
 * default Meteor Methods and Subscriptions.
 */
export function ServerModule (moduleName?: string) {
  return function (target: any) {
    boltModuleProperty(target.prototype);
    // Use either a passed-in name, or the class' name
    target.prototype.__moduleName = moduleName || target.name;
  }
}


/**
 * Take the name of the method to be exposed for Meteor,
 * and save it to the object's prototype for later. We
 * we do this so we can access each method for future 
 * registration with Meteor's 'method' function
 */
export function ServerMethod (name: string = null) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    let fnName = name || descriptor.value.name;
    // ensure we actually get the real prototype
    let proto = target.prototype ? target.prototype : target.constructor.prototype 

    if (!proto.__moduleFunctions) proto.__moduleFunctions = {};
    proto.__moduleFunctions[fnName] = descriptor.value;
  }
}

说明

您正在以一种可以阅读和理解的格式定义有关该类的其他信息。您在类内部使用的每种方法/属性都需要存储有关其自身的信息,并且 NOT 执行 ANY 操作。装饰器决不能引起任何外部副作用 ever 。我之所以仅将此作为重点,是因为您不想失去对代码库中事情发生情况的了解。

现在我们要看一些代码,我们必须避开讨厌的注册,并且不要失去对某些潜在绑定代码的访问权限。我们可以通过在类上新创建的__module属性来获得所需的一切,但是尚不能通过typescript看到。

此处有两个选项:

let myInstance: IServerModule & MyClass = new MyClass();
// or
let myInstance: any = new MyClass();

设置

但是,当您访问方法注册(express.get等)时,您想要一个引用该类的东西,将其存储在注册表中(实际上只是一些启动文件中的一个数组,类似于Angular的模块) ),然后将所有内容注册到该引导文件/模块文件中。

访问__module属性,读取存储的信息,然后根据需要进行注册。这样,您就可以将关注点分离,对应用程序中正在创建的内容有了清晰的了解,并可以完全根据自己的需要使用装饰器。