Як реалізувати декоратор машинопису?


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
декор

Я б запропонував поглянути на кілька прикладів цього проекту. Є кілька декораторів - деякі дуже прості, а деякі можуть бути трохи складнішими для розуміння: github.com/vlio20/utils-decorators
vlio20

Відповіді:


396

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

Загальні бали

  • Декоратори викликаються при оголошенні класу, а не тоді, коли об'єкт інстанціюється.
  • Для одного класу / властивості / методу / параметра можна встановити кілька декораторів.
  • Декоратори не дозволяються на конструкторах.

Дійсний декоратор повинен бути:

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

Довідково


Спосіб / формальний декоратор аксесуарів

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

  • 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");

Вихід:

Аргументи методу: ["тестування"]

Повертається значення: Тестування повідомлення

Примітки:

  • Не використовуйте синтаксис стрілок під час встановлення значення дескриптора. У цьому випадку контекст 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;
    };
}

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

Подібний до декоратора методу з деякими відмінностями:

  • Його 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/isissue/…
Ремо Х. Янсен

1
@OweRReLoaDeD Я додав приклад під декоратор параметрів, який просто записує те, що передано декоратору. Я не впевнений, чи це корисно, хоча. Наразі не можу придумати гарного прикладу.
Девід Шеррет

FYI Я зібрав та переробив цю інформацію на github: github.com/arolson101/typescript-decorators
arolson101

- прапор --experimentalDecorators повинен бути встановлений для того, щоб цей приклад працював
Trident D'Gao

Я трохи збентежений щодо того , що targetабо prototype of the classі keyвідноситься, може хто -то прохання уточнити , що?
Satej S

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

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


4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • target: прототип класу у наведеному вище випадку це "Foo"
  • propertyKey: назва методу, що викликається, у наведеному вище випадку "Boo"
  • дескриптор: опис об'єкта => містить властивість значення, яка, в свою чергу, є самою функцією: 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

Насправді це неправильно і не може бути компільований, потрібно фігурні дужки безпосередньо після повернення {value: ...}. Це навіть можна побачити з потенційного джерела вашого коду - blog.wolksoftware.com/…
PandaWood
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.