Как реализовать декоратор typescript?

TypeScript 1.5 теперь decorators.

Может ли кто-нибудь представить простой пример, демонстрирующий надлежащий способ реализации декоратора, и описать, что означают аргументы в допустимых знаках декоратора?

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Function, propertyKey: string | symbol, parameterIndex: number) => void;

Кроме того, существуют ли какие-либо соображения лучшей практики, которые следует учитывать при реализации декоратора?

Ответы

Ответ 1

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

Общие очки

  • Декораторы вызывается при объявлении класса, а не при создании объекта.
  • Несколько декораторов могут быть определены в одном классе /Property/Method/Parameter.
  • Декораторы не допускаются к конструкторам.

Действительный декоратор должен быть:

  1. Назначение одного из типов Decorator (ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator).
  2. Возвращает значение (в случае декораторов классов и декоратора), которое присваивается украшенному значению.

Справка


Метод/Формальный декоратор

Параметры реализации:

  • target: прототип класса (Object).
  • propertyKey: имя метода (string | symbol).
  • descriptor: TypedPropertyDescriptor Если вы не знакомы с дескрипторными ключами, я бы рекомендовал прочитать об этом в этой документации по Object.defineProperty (это третий параметр).

Пример - без аргументов

Использование:

class MyClass {
    @log
    myMethod(arg: string) { 
        return "Message -- " + arg;
    }
}

Реализация:

function log(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
    const originalMethod = descriptor.value; // save a reference to the original method

    // NOTE: Do not use arrow syntax here. Use a function expression in 
    // order to use the correct value of 'this' in this method (see notes below)
    descriptor.value = function(...args: any[]) {
        // pre
        console.log("The method args are: " + JSON.stringify(args));
        // run and store result
        const result = originalMethod.apply(this, args);
        // post
        console.log("The return value is: " + result);
        // return the result of the original method (or modify it before returning)
        return result;
    };

    return descriptor;
}

Входные данные:

new MyClass().myMethod("testing");

Вывод:

Аргументы метода: ["testing"]

Возвращаемое значение: Message-testing

Заметки:

Пример - с аргументами (фабрика декораторов)

При использовании аргументов вы должны объявить функцию с параметрами декоратора, а затем вернуть функцию с сигнатурой примера без аргументов.

class MyClass {
    @enumerable(false)
    get prop() {
        return true;
    }
}

function enumerable(isEnumerable: boolean) {
    return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
        descriptor.enumerable = isEnumerable;
        return descriptor;
    };
}

Декоратор статического метода

Как и у декоратора с некоторыми отличиями:

  • Его target параметр - это сама конструкторская функция, а не прототип.
  • Дескриптор определяется как функция конструктора, а не прототип.

Класс декоратора

@isTestable
class MyClass {}

Параметр реализации:

  • target: класс, на который объявлен декоратор (TFunction extends Function).

Пример использования: использование метаданных api для хранения информации о классе.


Декоратор объектов

class MyClass {
    @serialize
    name: string;
}

Параметры реализации:

  • target: прототип класса (Object).
  • propertyKey: имя свойства (string | symbol).

Пример использования: Создание @serialize("serializedName") и добавление имени свойства в список свойств для сериализации.


Декоратор параметров

class MyClass {
    myMethod(@myDecorator myParameter: string) {}
}

Параметры реализации:

  • target: прототип класса (Function -it кажется, что Function больше не работает. Теперь вы должны использовать any или Object здесь, чтобы использовать декоратор в любом классе. Или укажите типы (-и) класса, которые вы хотите ограничить к)
  • propertyKey: имя метода (string | symbol).
  • parameterIndex: индекс параметра в списке параметров функции (number).

Простой пример

Подробный пример (ы)

Ответ 2

Одна важная вещь, которую я не вижу в других ответах:

Декоратор factory

Если мы хотим настроить способ применения декоратора к объявлению, мы можем написать декоратор factory. Декоратор Factory - это просто функция, которая возвращает выражение, которое будет вызываться декоратором во время выполнения.

// This is a factory, returns one of ClassDecorator,
// PropertyDecorator, MethodDecorator, ParameterDecorator
function Entity(discriminator: string):  {
    return function(target) {
        // this is the decorator, in this case ClassDecorator.
    }
}

@Entity("cust")
export class MyCustomer { ... }

Ознакомьтесь с руководством TypeScript главой Decorators.

Ответ 3

class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • target: прототип класса в приведенном выше случае, это "Foo"
  • propertyKey: имя вызываемого метода, в приведенном выше случае "Boo"
  • descriptor: описание объекта = > содержит свойство value, которое, в свою очередь, является самой функцией: function (name) {return 'Hello' + name; }

Вы можете реализовать то, что регистрирует каждый вызов на консоли:

function consoleLogger(target: Function, key:string, value:any) 
{
  return value: (...args: any[]) => 
  {
     var a = args.map(a => JSON.stringify(a)).join();
     var result = value.value.apply(this, args);
     var r = JSON.stringify(result);

     console.log('called method' + key + ' with args ' + a + ' returned result ' + r);

     return result;
  }     
}