Typescript декораторы - свяжите метод декоратора с декоратором класса - PullRequest
0 голосов
/ 31 марта 2019

Я работаю над библиотекой, которая собирается реализовать некоторые пользовательские настройки маршрутизации веб-запросов. И я хочу иметь возможность реализовать эту функциональность, используя декораторы typescirpt, как это.

@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 ]

1 голос
/ 31 марта 2019

Я действительно сталкивался с этой проблемой прежде, и есть значительные осложнения.

Во-первых, может очень легко потерять доступ к значению "this", поэтому вы должны быть осторожны. Альтернатива состоит в том, чтобы рассматривать каждую функцию как статический, чистый метод, который просто определен в объекте. Второй - это порядок оценки декораторов, который, как вы, вероятно, уже знаете, вывернут наизнанку.

Имея в виду оба из них, это то, что я сделал. Я использовал этот код с Meteor, который сделал нечто очень похожее на то, что вы делаете.

Преамбула

ServerModule был просто классом с серией обработчиков методов. AKA контроллер, и этот код был построен для 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 действия. Декоратор никогда не должен вызывать какого-либо внешнего побочного эффекта когда-либо . Я только делаю это важным моментом, потому что вы не хотите забывать о том, как все происходит в вашей кодовой базе.

Теперь, когда у нас есть некоторый код для просмотра, мы должны обойти эту надоедливую регистрацию и не потерять доступ к некоторому потенциально связанному коду. У нас есть все, что нам нужно, через только что созданное свойство __module класса, но оно пока не видно в машинописи.

Два варианта здесь:

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

Настройка

Как бы вы ни обращались к регистрации метода (express.get и т. Д.), Вам нужно иметь что-то, что берет ссылку на класс, сохраняет его в реестре (буквально просто массив в каком-то загрузочном файле, аналогично модулям Angular. ) и регистрирует все в этом файле загрузки / модуля.

Откройте свойство __module, прочитайте информацию, которую вы сохранили, и зарегистрируйте ее при необходимости. Таким образом, вы выполняете разделение задач, у вас есть четкое понимание того, что создается в вашем приложении, и вы можете использовать свои декораторы именно так, как считаете нужным.

1 голос
/ 31 марта 2019

И, конечно, я работаю над этой проблемой вскоре после публикации. Мне просто нужно сравнить прототип цели класса parent с целью метода child, и они совпадают.

...