Использование TypeScript Decorator в методах класса, реализованных в виде функции стрелки - PullRequest
0 голосов
/ 23 апреля 2020

Отказ от ответственности: я прошел через аналогичные вопросы, размещенные на SO раньше. Они помогли мне исправить некоторые TypeScript проблемы с компилятором. Но я не смог найти работающий, полный пример кода.

Проблема: я хочу использовать декоратор TypeScript в некоторых моих методах класса. Но проблема в том, что я не могу использовать их, если мои методы реализованы как функции стрелок. Я изменил свой код (так как во время выполнения они рассматриваются скорее как свойства класса), основываясь на некоторых статьях и SO сообщениях, в основном

  1. Необязательный дескриптор descriptor?: TypedPropertyDescriptor<any>

  2. Возврат как any

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

Декоратор

// Decorator declaration
export function LogPerf(
    message = '',
) {
    return function (
        target: any,
        key: string | symbol,
        descriptor?: TypedPropertyDescriptor<any>,
    ) {
        if (!descriptor) {
            console.log('Logging performance of property...'); // This log doesn't execute at runtime
            return;
        }

        // following log also doesn't work
        const original = descriptor.value;

        descriptor.value = function (...args: any[]) {
            const timeStartMilli: number = performance.now();

            const result = original.apply(this, args);

            const timeEndMilli: number = performance.now();

            console.log(`Call to ${key.toString()} took ${timeEndMilli - timeStartMilli} ms. Message: ${message}`);

            return result;
        };

        return descriptor as any;
    };
}

И пример использования, который не работает.

ПРИМЕЧАНИЕ: Это работает нормально при использовании в обычных методах экземпляра.

// Decorater use (on class method, implemented as arrow function)
public export SomeClass {

    @LogPerf()
    public myMethod = (
        data: MyData,
    ): Somehting => {
        // Do something with data
    }
}

1 Ответ

1 голос
/ 23 апреля 2020

Проблема в том, что декораторы предназначены для применения к prototype объекту. Если вы проверите проверенный js, вы поймете, почему он не работает.

class SomeClass {
    @LogPerf()
    public myMethod = (
        data: MyData,
    ): Somehting => {
        // Do something with data
    }
}

/** transpiled to: */

class SomeClass {
    constructor() {
        this.myMethod = (data) => {
            // Do something with data
        };
    }
}

__decorate([
    LogPerf()
], SomeClass.prototype, "myMethod", void 0);

Видите, декоратор применяется к SomeClass.prototype["myMethod"]. Однако, когда вы определяете ваш метод как функцию стрелки, он инициализируется в конструкторе и присваивается объекту this.

Обходной путь будет:

  1. определить ваш метод как нормальную функцию на prototype объекте, затем связать this при инициализации.
  2. перегрузка LogPerf() подпись, чтобы принять альтернативное использование, подобное следующему:
class SomeClass {
    public myMethod = LogPerf()((
        data: MyData,
    ): Somehting => {
        // Do something with data
    })
}

Метод 1 может быть интегрирован в @LogPerf декоратор, если вы предпочитаете. Вы можете использовать этот фрагмент кода из autobind-decorator lib.

function boundMethod(target, key, descriptor) {
  var fn = descriptor.value;

  if (typeof fn !== 'function') {
    throw new TypeError("@boundMethod decorator can only be applied to methods not: ".concat(_typeof(fn)));
  } // In IE11 calling Object.defineProperty has a side-effect of evaluating the
  // getter for the property which is being replaced. This causes infinite
  // recursion and an "Out of stack space" error.


  var definingProperty = false;
  return {
    configurable: true,
    get: function get() {
      // eslint-disable-next-line no-prototype-builtins
      if (definingProperty || this === target.prototype || this.hasOwnProperty(key) || typeof fn !== 'function') {
        return fn;
      }

      var boundFn = fn.bind(this);
      definingProperty = true;
      Object.defineProperty(this, key, {
        configurable: true,
        get: function get() {
          return boundFn;
        },
        set: function set(value) {
          fn = value;
          delete this[key];
        }
      });
      definingProperty = false;
      return boundFn;
    },
    set: function set(value) {
      fn = value;
    }
  };
}

Часть реализации метода 2 тривиальна, вам просто нужно обработать случай, когда только одна функция arg Я думаю, что вы можете понять это.

Сложная задача - как удовлетворить TypeScript, вот подпись функции, которая вам нужна:

export function LogPerf(message?: string): {
    <T extends Function>(fn: T): T;
    (target: Object, key: string | symbol, descriptor?: TypedPropertyDescriptor<any> | undefined): any;
}
export function LogPerf(message = '') {

  // YOUR IMPLEMENTATION GOES HERE

}

Typescript Playground

...