Как реализовать декоратор машинописи?
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 ответа:
я закончил играть с декораторами и решил документировать то, что я выяснил для тех, кто хочет воспользоваться этим, прежде чем появится какая-либо документация. Пожалуйста, не стесняйтесь редактировать это, если вы видите какие-либо ошибки.
Общие Положения
- декораторы вызываются при объявлении класса, а не при создании экземпляра объекта.
- несколько декораторов могут быть определены на одном и том же Класс / Свойство / Метод / Параметр.
- декораторы не допускаются на конструкторы.
действительный декоратор должен быть:
- присваивается одному из типов декоратора (
ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator
).- возвращает значение (в случае декораторов класса и декоратора метода), которое присваивается украшенному значению.
Способ / Официально Аксессуар Декоратор
реализация параметров:
target
: прототип класса (Object
).propertyKey
: название метода (string
/symbol
).descriptor
: ATypedPropertyDescriptor
- если вы не знакомы с ключами дескриптора, я бы рекомендовал прочитать об этом в документация onObject.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
).подробный пример(с)
- Memoize декоратор - метод, Get / Set Accessor decorator example
одна важная вещь, которую я не вижу в других ответах:
фабрика декоратора
Если мы хотим настроить, как декоратор применяется к объявлению, мы можем написать фабрику декоратора. Фабрика декоратора-это просто функция, которая возвращает выражение, которое будет называться декоратором во время выполнения.
// 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; } }