Как реализовать декоратор машинописи?


TypeScript 1.5 Теперь декораторы.

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

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;

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

3 170

3 ответа:

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

Общие Положения

  • декораторы вызываются при объявлении класса, а не при создании экземпляра объекта.
  • несколько декораторов могут быть определены на одном и том же Класс / Свойство / Метод / Параметр.
  • декораторы не допускаются на конструкторы.

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

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

ссылка


Способ / Официально Аксессуар Декоратор

реализация параметров:

  • target: прототип класса (Object).
  • propertyKey: название метода (string/symbol).
  • descriptor: A TypedPropertyDescriptor - если вы не знакомы с ключами дескриптора, я бы рекомендовал прочитать об этом в документация on 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");

выход:

метод args являются: ["тестирование"]

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

Примечания:

  • не используйте синтаксис стрелки при установке значения дескриптора. контекст this не будет экземпляра, если вы это сделаете.
  • лучше изменить исходный дескриптор, чем перезаписывать текущий, возвращая новый дескриптор. Это позволяет использовать несколько декораторов, которые редактируют дескриптор без перезаписи того, что сделал другой декоратор. Это позволяет использовать что-то вроде @enumerable(false) и @log в то же время (например: плохо vs хороший)
  • полезное тип аргумент TypedPropertyDescriptor может использоваться для ограничения сигнатур метода (Пример Метода) или подписи доступа (Аксессу Пример) декоратор можно надеть.

Пример - С Аргументами (Фабрика Декоратора)

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

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-кажется Function больше не работает. Вы должны использовать any или Object вот теперь, чтобы использовать декоратор в любом классе. Или укажите тип(ы) класса, который вы хотите ограничить)
  • propertyKey: название метод (string/symbol).
  • parameterIndex: индекс параметра в списке параметров функции (number).

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

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

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

фабрика декоратора

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

// 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 { ... }

проверьте руководство по машинописи глава декораторов.

class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • цель: прототип класса в приведенном выше случае это "Foo"
  • propertyKey: имя вызываемого метода, в приведенном выше случае "Boo"
  • дескриптор: описание объекта = > содержит свойство 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;
  }     
}