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;
Кроме того, существуют ли какие-либо соображения лучшей практики, которые следует учитывать при реализации декоратора?
Я закончил играть с декораторами и решил записать то, что я понял для всех, кто хочет воспользоваться этим, прежде чем выйдет какая-либо документация. Не стесняйтесь редактировать это, если увидите какие-либо ошибки.
Действительный декоратор должен быть:
- Назначение одного из типов Decorator (
ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator
).- Возвращает значение (в случае декораторов классов и декоратора), которое присваивается украшенному значению.
Параметры реализации:
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"]
Возвращаемое значение: Message-testing
Заметки:
this
не будет экземпляром, если вы это сделаете.@enumerable(false)
и @log
одновременно (пример: ) {
return {
value: function (...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(`Call: ${propertyKey}(${a}) => ${r}`);
return result;
}
};
}
function enumerable(isEnumerable: boolean) {
return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor) => {
descriptor.enumerable = isEnumerable;
return descriptor;
}
}
var test = new FooBar();
test.foo("asdf"); rel=noreferrer>Bad vs ) {
var originalMethod = value.value;
value.value = function (...args: any[]) {
var a = args.map(a => JSON.stringify(a)).join();
var result = originalMethod.apply(this, args);
var r = JSON.stringify(result);
console.log(`Call: ${propertyKey}(${a}) => ${r}`);
return result;
};
return value;
}
function enumerable(isEnumerable: boolean) {
return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor) => {
descriptor.enumerable = isEnumerable;
return descriptor;
}
}
var test = new FooBar();
test.foo("asdf"); rel=noreferrer>Good)TypedPropertyDescriptor
может использоваться для ограничения того, какие сигнатуры методов ( number>) {
console.log('This descriptor is only allowed on methods that have one parameter of type number and return a number.');
return descriptor;
} rel=noreferrer>пример метода) или сигнатуры доступа () {
console.log('This descriptor is only allowed on accessors that return a number.');
return descriptor;
} rel=noreferrer>пример 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;
};
}
Как и у декоратора с некоторыми отличиями:
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
-it кажется, что Function
больше не работает. Теперь вы должны использовать any
или Object
здесь, чтобы использовать декоратор в любом классе. Или укажите типы (-и) класса, которые вы хотите ограничить к)propertyKey
: имя метода (string
| symbol
).parameterIndex
: индекс параметра в списке параметров функции (number
).Одна важная вещь, которую я не вижу в других ответах:
Если мы хотим настроить способ применения декоратора к объявлению, мы можем написать декоратор factory. Декоратор Factory - это просто функция, которая возвращает выражение, которое будет вызываться декоратором во время выполнения.
// 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 главой Decorators.
class Foo {
@consoleLogger
Boo(name:string) { 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;
}
}
@Injectable
в декоратор, обратитесь