Как реализовать декоратор typescript?
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
).- возвращает значение (в случае декораторов класса и декоратора метода), которое присваивается украшенному значению.
Способ / Официально Accessor Decorator
реализация параметров:
-
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;
};
}
Статический Метод Декоратор!--42-->
похож на декоратора метода с некоторыми отличиями:
- его
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 пример
одна важная вещь, которую я не вижу в других ответах:
фабрика декоратора
Если мы хотим настроить, как декоратор применяется к объявлению, мы можем написать фабрику декоратора. Фабрика декораторов-это просто функция, возвращающая выражение, которое будет вызываться декоратором во время выполнения.
// 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 глава декораторов.
class Foo {
@consoleLogger
Boo(name:string) { return "Hello, " + name }
}
- target: прототип класса в приведенном выше случае это "Foo"
- propertyKey: имя вызываемого метода, в приведенном выше случае "Boo"
- descriptor: description of object => содержит свойство 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;
}
}