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

207

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;

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

Дэвид Шеррет
источник
Примечание для себя :-), если вы хотите @Injectableдобавить в декоратор, обратитесь
Anand Rockzz
Я бы посоветовал взглянуть на несколько примеров этого проекта. Существует несколько декораторов - некоторые очень просты, а некоторые могут быть немного сложнее для понимания: github.com/vlio20/utils-decorators
vlio20

Ответы:

396

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

Общие пункты

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

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

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

Ссылка


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

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

  • target: Прототип класса ( Object).
  • propertyKey: Название метода ( string| symbol).
  • descriptor: A 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одновременно (пример: плохо против хорошего )
  • Полезно : Аргумент типа 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;
    };
}

Статический метод 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).

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

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

Дэвид Шеррет
источник
Вы знаете, где найти пример «Декоратор параметров»? Я пытался реализовать один безуспешно github.com/Microsoft/TypeScript/issues/…
Ремо Х. Янсен
1
@OweRReLoaDeD Я добавил пример под параметром декоратор, который просто записывает то, что было передано декоратору. Я не уверен, если это полезно, хотя. Я не могу придумать хороший пример в данный момент.
Дэвид Шеррет
К вашему сведению я собрал и настроил эту информацию на github: github.com/arolson101/typescript-decorators
arolson101
--experimentalDecorators флаг должен быть установлен для того, чтобы этот пример работал
Trident D'Gao
Я немного запутался в том, что targetили что prototype of the classи keyотносится, может кто-нибудь, пожалуйста, уточните это?
Сатей С
8

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

Фабрика декораторов

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

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

Ондра Жижка
источник
4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • target: прототип класса в приведенном выше случае это "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;
  }     
}
Эрик Либен
источник
1
Трудно заставить его скомпилировать со строгими настройками компилятора
PandaWood
На самом деле, это неверно и не может быть скомпилировано, необходимо использовать фигурные скобки непосредственно после return {value: ...}. Это можно увидеть даже из потенциального источника вашего кода - blog.wolksoftware.com/…
PandaWood