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




decorator (2)

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. Назначается одному из типов декораторов ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator ).
  2. Вернуть значение (в случае декораторов классов и методов декораторов), которое присваивается декорированному значению.

Reference

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

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

  • 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"]

Возвращаемое значение: Сообщение - тестирование

Заметки:

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

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

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

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;
    };
}

Статический метод Decorator

Похож на метод декоратор с некоторыми отличиями:

  • Его 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 { ... }

Обратитесь к главе «Декораторы» руководства TypeScript.





decorator