- PVSM.RU - https://www.pvsm.ru -
Декораторы — это невероятно круто. Они позволяют описывать мета информацию прямо в объявлении класса, группируя все в одном месте и избегая дублирования. Ужасно удобно. Однажды попробовав, вы уже никогда не согласитесь писать по-старому.
Однако, несмотря на всю полезность, декораторы в TypeScript (заявлены [1] также на стандарт) не так просты, как хотелось бы. Работа с ними требует навыков джедая, так как необходимо разбираться в объектной модели JavaScript [2] (ну, вы поняли, о чем я), API несколько запутанный и, к тому же, еще не стабильный. В этой статье я немного расскажу об устройстве декораторов и покажу несколько конкретных приемов, как поставить эту темную силу на благо front-end разработки.
Помимо TypeScript, декораторы доступны в Babel [3]. В этой статье рассматривается только реализация в TypeScript.
Декорировать в TypeScript можно классы, методы, параметры метода, методы доступа свойства (accessors) и поля.
В TypeScript термин "поле" обычно не используется, и поля называют также свойствами [4] (property). Это создает большую путаницу, т.к. разница есть. Если мы объявляем свойство с методами доступа get/set, то в объявлении класса появляется [5] вызов Object.defineProperty и в декораторе доступен дескриптор, а если объявляем просто поле (в терминах C# и Java) — то не появляется [6] ничего, и, соответственно, дескриптор не передается в декоратор. Это определяет сигнатуру декораторов, поэтому я использую термин "поле", чтобы отличать их от свойств с методами доступа.
В общем случае, декоратор — это выражение, предваренное символом "@", которое возвращает функцию определенного вида (разного в каждом случае [7]). Собственно, можно просто объявить такую функцию и использовать ее имя в качестве выражения декоратора:
function MyDecorator(target, propertyKey, descriptor) {
// ...
}
class MyClass {
@MyDecorator
myMethod() {
}
}
Однако можно использовать любое другое выражение, которое вернет такую функцию. Например, можно объявить другую функцию, которая будет принимать параметрами дополнительную информацию, и возвращать соответствующую лямбду. Тогда в качестве декоратора будем использовать выражение "вызов функции MyAdvancedDecorator".
function MyAdvancedDecorator(info?: string) {
return (target, propertyKey, descriptor) => {
// ..
};
}
class MyClass {
@MyAdvancedDecorator("advanced info")
myMethod() {
}
}
Здесь самый обычный вызов функции, поэтому, даже если мы не передаем параметры, все равно нужно писать скобки "@MyAdvancedDecorator()". Собственно, это два основных способа объявления декораторов.
В процессе компиляции объявление декоратора приводит к появлению вызова нашей функции в определении класса. То есть там, где вызываются Object.defineProperty
, заполняется прототип класса и все такое. Как именно это происходит — важно знать, т.к. это объясняет, когда вызывается декоратор, что представляют собой параметры нашей функции, почему они именно такие, а также что и как в декораторе можно сделать. Ниже приведен упрощенный код, в который компилируется наш класс с декоратором:
var __decorateMethod = function (decorators, target, key) {
var descriptor = Object.getOwnPropertyDescriptor(target, key);
for (var i = decorators.length - 1; i >= 0; i--) {
var decorator = decorators[i];
descriptor = decorator(target, key, descriptor) || descriptor; // Вызов функции декоратора
}
Object.defineProperty(target, key, descriptor);
};
// Объявление класса MyClass
var MyClass = (function () {
function MyClass() {} // Конструктор
MyClass.prototype.myMethod = function () { }; // метод myMethod
// Вызов декораторов
__decorateMethod([
MyAdvancedDecorator("advanced info") // Вычисление выражения декоратора, и получение функции
], MyClass.prototype, "myMethod");
return MyClass;
}());
В таблице ниже приведено описание функции для каждого вида декораторов, а также ссылки на примеры в TypeScript Playground [8], где можно посмотреть, во что точно компилируются декораторы и попробовать их в действии.
Вид декоратора | Сигнатура функции |
---|---|
Декоратор класса [9] Пример в playground [10] @MyDecorator class MyClass {}
|
function MyDecorator<TFunction extends Function>(target: TFunction): TFunction { return target; }
|
Декоратор метода [11] Пример в playground [12] class MyClass { @MyDecorator myMethod(){} }
|
function MyDecorator(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> { return descriptor; }
|
Декоратор статического метода [11] Пример в playground [15] class MyClass { @MyDecorator static myMethod(){} }
|
function MyDecorator(target: Function, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> { return descriptor; }
|
Декоратор методов доступа [16] Пример в playground [17] class MyClass { @MyDecorator get myProperty(){} }
|
Аналогично методу. Декоратор следует применять к первому методу доступа (get или set), в порядке объявления в классе. |
Декоратор параметра [18] Пример в playground [19] class MyClass { myMethod( @MyDecorator val){ } }
|
function MyDecorator(target: Object, propertyKey: string | symbol, index: number): void { }
|
Декоратор поля (свойства) [20] Пример в playground [21] class MyClass { @MyDecorator myField: number; }
|
function MyDecorator(target: Object, propertyKey: string | symbol): TypedPropertyDescriptor<any> { return null; }
|
Декоратор статического поля (свойства) [20] Пример в playground [23] class MyClass { @MyDecorator static myField; }
|
function MyDecorator(target: Function, propertyKey: string | symbol): TypedPropertyDescriptor<any> { return null; }
|
Интерфейсы | Декораторы интерфейсов и их членов не поддерживаются. |
Объявления типов | Декораторы в объявлениях типов (ambient declarations) не поддерживаются. |
Функции и переменные вне класса | Декораторы вне класса не поддерживаются. |
Интерфейс TypedPropertyDescriptor<T>, фигурирующий в сигнатуре декораторов методов и свойств объявлен следующим образом:
interface TypedPropertyDescriptor<T> {
enumerable?: boolean;
configurable?: boolean;
writable?: boolean;
value?: T;
get?: () => T;
set?: (value: T) => void;
}
Если указать в объявлении декоратора конкретный тип T для TypedPropertyDescriptor, то можно ограничить тип свойств, к которым декоратор применим. Что означают члены этого интерфейса — можно посмотреть здесь [13]. Если коротко, для метода value содержит собственно сам метод, для поля — значение, для свойства — get и set содержат соответствующие методы доступа.
Поддержка декораторов экспериментальная и может измениться в будущих релизах (в TypeScript 2.0 [24] не изменилась). Поэтому необходимо добавить experimentalDecorators: true в tsconfig.json. Кроме того, декораторы доступны только если target: es5 или выше.
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
Важно!!! — о target: ES3 и JSFiddle
Важно не забыть указать опцию target — ES5 при работе с декораторами. Если этого не сделать, то код скомпилируется без ошибок, но работать будет по-другому (это баг в компиляторе TypeScript [25]). В частности, декораторам методов и свойств не будет передаваться третий параметр, а их возвращаемое значение будет игнорироваться.
Эти феномены можно наблюдать в JSFiddle (это уже баг в JSFiddle [26]), поэтому в данной статье я не размещаю примеры в JSFiddle.
Тем не менее, есть обходное решение для этих багов. Нужно просто самим получать дескриптор, и самим же его обновлять. Например, вот реализация декоратора @safe [27], которая работает как с target ES3, так и с ES5.
Для использования информации о типах необходимо также добавить emitDecoratorMetadata: true.
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Для использования класса Reflect [28] необходимо установить дополнительный пакет reflect-metadata [29]:
npm install reflect-metadata --save
И в коде:
import "reflect-metadata";
Однако если вы используете Angular 2, то ваша система сборки уже может содержать в себе реализацию Reflect, и после установки пакета reflect-metadata вы можете получить runtime ошибку Unexpected value 'YourComponent' exported by the module 'YourModule'
. В этом случае лучше установить только typings [30].
typings install dt~reflect-metadata --global --save
Итак, перейдем к практике. Рассмотрим несколько примеров, демонстрирующих возможности декораторов.
Допустим, у нас часто встречаются второстепенные функции, ошибки внутри которых мы хотели бы игнорировать. Писать каждый раз try/catch громоздко, на помощь приходит декоратор:
function safe(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> {
// Запоминаем исходную функцию
var originalMethod = descriptor.value;
// Подменяем ее на нашу обертку
descriptor.value = function SafeWrapper () {
try {
// Вызываем исходный метод
originalMethod.apply(this, arguments);
} catch(ex) {
// Просто выводим в консоль, исполнение кода будет продолжено
console.error(ex);
}
};
// Обновляем дескриптор
return descriptor;
}
class MyClass {
@safe public foo(str: string): boolean {
return str.length > 0; // если str == null, будет ошибка
}
}
var test = new MyClass();
console.info("Starting...");
test.foo(null);
console.info("Continue execution");
Результат выполнения:
Попробовать в действии в Plunker [31]
Посмотреть в Playground [32]
Допустим, при изменении значения поля нужно выполнить какую-то логику. Можно, конечно, определить свойство с get/set методами, и в set поместить нужный код. А можно сократить объем кода, объявив декоратор:
function OnChange<ClassT, T>(callback: (ClassT, T) => void): any {
return (target: Object, propertyKey: string | symbol) => {
// Необходимо задействовать существующий дескриптор, если он есть.
// Это позволит объявять несколько декораторов на одном свойстве.
var descriptor = Object.getOwnPropertyDescriptor(target, propertyKey)
|| {configurable: true, enumerable: true};
// Подменяем или объявляем get и set
var value: T;
var originalGet = descriptor.get || (() => value);
var originalSet = descriptor.set || (val => value = val);
descriptor.get = originalGet;
descriptor.set = function(newVal: T) {
// Внимание, если определяем set через function,
// то this - текущий экземпляр класса,
// если через лямбду, то this - Window!!!
var currentVal = originalGet.call(this);
if (newVal != currentVal) {
// Вызываем статический метод callback с двумя параметрами
callback.call(target.constructor, this, newVal);
}
originalSet.call(this, newVal);
};
// Объявляем новое свойство, либо обновляем дескриптор
Object.defineProperty(target, propertyKey, descriptor);
return descriptor;
}
}
Обратите внимание, мы вызываем defineProperty и возвращаем дескриптор из декоратора. Это связано с багом в reflect-metadata [22], из-за которого для декоратора полей возвращаемое значение игнорируется.
class MyClass {
@OnChange(MyClass.onFieldChange)
public mMyField: number = 42;
static onFieldChange(self: MyClass, newVal: number): void {
console.info("Changing from " + self.mMyField + " to " + newVal);
}
}
var test = new MyClass();
test.mMyField = 43;
test.mMyField = 44;
Результат выполнения:
» Попробовать в действии в Plunker [33]
» Посмотреть в Playground [34]
Нам пришлось обработчик объявить как static, т.к. трудно сосласться на экземплярный метод. Вот альтернативный вариант со строковым параметром [35], и другой с использованием лямбды [36].
Одной из интересных особенностей декораторов является возможность получать информацию о типе декорируемого свойства или параметра (скажем "спасибо" Angular, т.к. сделано было специально для него). Чтобы это заработало, нужно подключить библиотеку reflect-metadata, и включить опцию emitDecoratorMetadata (см. выше). После этого для свойств, которые имеют хотя бы один декоратор, можно вызвать Reflect.getMetadata с ключем "design:type", и получить конструктор соответствующего типа. Ниже простая реализация декоратора @Inject
, который использует этот прием для внедрения зависимостей:
// Объявляем декоратор
function Inject(target: Object, propKey: string): any {
// Получаем конструктор типа свойства
// (в примере ниже это будет конструктор класса ILogService)
var propType = Reflect.getMetadata("design:type", target, propKey);
// Переопределяем декорируемое свойство
var descriptor = {
get: function () {
// this - текущий объект класса
var serviceLocator = this.serviceLocator || globalSericeLocator;
return serviceLocator.getService(propType);
}
};
Object.defineProperty(target, propKey, descriptor);
return descriptor;
}
Обратите внимание, мы вызываем defineProperty и возвращаем дескриптор из декоратора. Это связано с багом в reflect-metadata [22], из-за которого для декоратора полей возвращаемое значение игнорируется.
// Использовать интерфейс, к сожалению, не получится
abstract class ILogService {
abstract log(msg: string): void;
}
class Console1LogService extends ILogService {
log(msg: string) { console.info(msg); }
}
class Console2LogService extends ILogService {
log(msg: string) { console.warn(msg); }
}
var globalSericeLocator = new ServiceLocator();
globalSericeLocator.registerService(ILogService, new ConsoleLogService1());
class MyClass {
@Inject
private logService: ILogService;
sayHello() {
this.logService.log("Hello there");
}
}
var my = new MyClass();
my.sayHello();
my.serviceLocator = new ServiceLocator();
my.serviceLocator.registerService(ILogService, new ConsoleLogService2());
my.sayHello();
class ServiceLocator {
services: [{interfaceType: Function, instance: Object }] = [] as any;
registerService(interfaceType: Function, instance: Object) {
var record = this.services.find(x => x.interfaceType == interfaceType);
if (!record) {
record = { interfaceType: interfaceType, instance: instance};
this.services.push(record);
} else {
record.instance = instance;
}
}
getService(interfaceType: Function) {
return this.services.find(x => x.interfaceType == interfaceType).instance;
}
}
Как видно, мы просто объявляем поле logService, а декоратор уже самостоятельно определяет его тип, и задает метод доступа, который получает соответствующий экземпляр сервиса. Красиво и удобно. Результат выполнения:
» Попробовать в Plunker [37]
» Посмотреть в Playground [38]
Допустим, по каким-то причинам необходимо переименовать некоторые поля объекта при сериализации в JSON. С помощью декоратора мы сможем объявить JSON-имя поля, а после, при сериализации, его прочитать. Технически данный декоратор иллюстрирует работу библиотеки reflect-metadata, а, в частности, функций Reflect.defineMetadata и Reflect.getMetadata.
// Уникальный ключ для наших метаданных
const JsonNameMetadataKey = "Habrahabr_PFight77_JsonName";
// Декоратор
function JsonName(name: string) {
return (target: Object, propertyKey: string) => {
// Сохраняем в метаданных переднный name
Reflect.defineMetadata(JsonNameMetadataKey, name, target, propertyKey);
}
}
// Функция, работающая в паре с декоратором
function serialize(model: Object): string {
var result = {};
var target = Object.getPrototypeOf(model);
for(var prop in model) {
// Загружаем сохраненное декоратором значение
var jsonName = Reflect.getMetadata(JsonNameMetadataKey, target, prop) || prop;
result[jsonName] = model[prop];
}
return JSON.stringify(result);
}
class Model {
@JsonName("name")
public title: string;
}
var model = new Model();
model.title = "Hello there";
var json = serialize(model);
console.info(JSON.stringify(moel));
console.info(json);
Результат выполнения:
» Попробовать в Plunker [39]
» Посмотреть в Playground [40]
Приведенный декоратор обладает тем недостатком, что, если модель содержит в качестве полей объекты других классов, то поля этих классов никак не обрабатываются методом serialize (то есть к ним нельзя применить декоратор @JsonName). Кроме того, здесь не реализовано обратное преобразование — из JSON в клиентскую модель. Оба этих недостатка исправлены в несколько более сложной реализации конвертера серверных моделей, в спойлере ниже.
@ServerModelField — конвертер серверных моделей на декораторах
Постановка задачи следующая. С сервера к нам прилетают некоторые JSON-данные примерно такого вида (похожий JSON шлет один BaaS сервис):
{
"username":"PFight77",
"email":"test@gmail.com",
"doc": {
"info":"The author of the article"
}
}
Мы хотим конвертировать эти данные в типизированный объект, переименовывая некоторые поля. Выглядеть в конечном счете все будет так:
class UserAdditionalInfo {
@ServerModelField("info")
public mRole: string;
}
class UserInfo {
@ServerModelField("username")
private mUserName: string;
@ServerModelField("email")
private mEmail: string;
@ServerModelField("doc")
private mAdditionalInfo: UserAdditionalInfo;
public get DisplayName() {
return mUserName + " " + mAdditionalInfo.mRole;
}
public get ID() {
return mEmail;
}
public static parse(jsonData: string): UserInfo {
return convertFromServer(JSON.parse(jsonData), UserInfo);
}
public serialize(): string {
var serverData = convertToServer(this);
return JSON.stringify(serverData);
}
}
Разберем, как это реализовано.
Во-первых, нам необходимо определить декоратор поля [41] ServerModelField, который будет принимать строковый параметр и сохранять его в метаданных. Кроме того, для разбора JSON нам еще нужно знать, какие поля с нашим декоратором есть в классе вообще. Для этого объявим еще один экземпляр метаданных, общий для всех полей класса, в котором и сохраним имена всех декорированных членов. Здесь мы уже будем не только сохранять метаданные через Relect.defineMetadata, но и получать через Reflect.getMetadata.
// Объявляем уникальные ключи, по которым будем идентифицировать наши метаданные
const ServerNameMetadataKey = "Habrahabr_PFight77_ServerName";
const AvailableFieldsMetadataKey = "Habrahabr_PFight77_AvailableFields";
// Объявляем декоратор
export function ServerModelField(name?: string) {
return (target: Object, propertyKey: string) => {
// Сохраняем в метаданных переданный name, либо название самого свойства, если параметр не задан
Reflect.defineMetadata(ServerNameMetadataKey, name || propertyKey, target, propertyKey);
// Проверяем, не определены ли уже availableFields другим экземпляром декоратора
var availableFields = Reflect.getMetadata(AvailableFieldsMetadataKey, target);
if (!availableFields) {
// Ok, мы первые, значит создаем новый массив
availableFields = [];
// Не передаем 4-й параметр(propertyKey) в defineMetadata,
// т.к. метаданные общие для всех полей
Reflect.defineMetadata(AvailableFieldsMetadataKey, availableFields, target);
}
// Регистрируем текущее поле в метаданных
availableFields.push(propertyKey);
}
}
Ну и осталось написать функцию convertFromServer. В ней почти нет ничего особенного, она просто вызывает Reflect.getMetadata и использует полученные метаданные для разбора JSON. Одна особенность — эта функция должна создать экземпляр UserInfo через new, поэтому мы передаем ей помимо JSON-данных еще и класс: convertFromServer(JSON.parse(data), UserInfo)
. Чтобы понять, как это работает, посмотрите спойлер ниже.
class MyClass {
}
// Объявляем переменную типа "конструктор класса без параметров"
var myType: { new(): any; };
// Присваиваем переменной наш класс
myType = MyClass;
// Эквивалентно new MyClass()
var obj = new myType();
Вторая особенность — это использование данных о типе поля, генерируемых благодаря настройке "emitDecoratorMetadata": true в tsconfig.json. Прием заключается в вызове Reflect.getMetadata
с ключом "design:type", который возвращает конструктор соответствующего типа. Например, вызов Reflect.getMetadata("design:type", target, "mAdditionalInfo")
вернет конструктор UserAdditionalInfo
. Мы будем использовать эту информацию для того, чтобы правильно обрабатывать поля пользовательских типов. Например, класс UserAdditionalInfo также использует декоратор @ServerModelField, поэтому мы должны также использовать эти метаданные для анализа JSON.
Третья особенность заключается в получении соответствующего target, откуда мы будем брать метаданные. Мы используем декораторы полей [41], поэтому метаданные нужно брать из прототипа класса. Для декораторов статических членов нужно использовать конструктор класса. Получить прототип можно, вызвав Object.getPrototypeOf [42] или же обратившись к свойству prototype конструктора.
Все остальные комментарии в коде:
export function convertFromServer<T>(serverObj: Object, type: { new(): T ;} ): T {
// Создаем объект, с помощью конструктора, переданного в параметре type
var clientObj: T = new type();
// Получаем контейнер с метаданными
var target = Object.getPrototypeOf(clientObj);
// Получаем из метаданных, какие декорированные свойства есть в классе
var availableNames = Reflect.getMetadata(AvailableFieldsMetadataKey, target) as [string];
if (availableNames) {
// Обрабатываем каждое свойство
availableNames.forEach(propName => {
// Получаем из метаданных имя свойства в JSON
var serverName = Reflect.getMetadata(ServerNameMetadataKey, target, propName);
if (serverName) {
// Получаем значение, переданное сервером
var serverVal = serverObj[serverName];
if (serverVal) {
var clientVal = null;
// Проверяем, используются ли в классе свойства декораторы @ServerModelField
// Получаем конструктор класса
var propType = Reflect.getMetadata("design:type", target, propName);
// Смотрим, есть ли в метаданных класса информация о свойствах
var propTypeServerFields = Reflect.getMetadata(AvailableFieldsMetadataKey, propType.prototype) as [string];
if (propTypeServerFields) {
// Да, класс использует наш декоратор, обрабатываем свойство рекурсивно
clientVal = convertFromServer(serverVal, propType);
} else {
// Нет, просто копируем значение
clientVal = serverVal;
}
// Записываем результат в конечный объект
clientObj[propName] = clientVal;
}
}
});
} else {
errorNoPropertiesFound(getTypeName(type));
}
return clientObj;
}
function errorNoPropertiesFound<T>(typeName: string) {
throw new Error("There is no @ServerModelField directives in type '" + typeName + "'. Nothing to convert.");
}
function getTypeName<T>(type: { new(): T ;}) {
return parseTypeName(type.toString());
}
function parseTypeName(ctorStr: string) {
var matches = ctorStr.match(/w+/g);
if (matches.length > 1) {
return matches[1];
} else {
return "<can not determine type name>";
}
}
Аналогичный вид имеет обратная функция — convertToServer.
function convertToServer<T>(clientObj: T): Object {
var serverObj = {};
var target = Object.getPrototypeOf(clientObj);
var availableNames = Reflect.getMetadata(AvailableFieldsMetadataKey, target) as [string];
availableNames.forEach(propName=> {
var serverName = Reflect.getMetadata(ServerNameMetadataKey, target, propName);
if (serverName) {
var clientVal = clientObj[propName];
if (clientVal) {
var serverVal = null;
var propType = Reflect.getMetadata("design:type", target, propName);
var propTypeServerFields = Reflect.getMetadata(AvailableFieldsMetadataKey, propType.prototype) as [string];
if (clientVal && propTypeServerFields) {
serverVal = convertToServer(clientVal);
} else {
serverVal = clientVal;
}
serverObj[serverName] = serverVal;
}
}
});
if (!availableNames) {
errorNoPropertiesFound(parseTypeName(clientObj.constructor.toString()));
}
return serverObj;
}
Работу декоратора @ServerModelField в действии можно посмотреть в plunker [43].
В ASP.NET сервер, как правило, состоит из контроллеров, которые содержат методы. Соответственно, url методов выглядит обычно, как /ControllerName/ActionName. В клиентском коде хорошей практикой будет сделать единую точку, через которую будут происходить все запросы к серверу вообще, и к каждому контроллеру в частности. Это позволит упросить рефакторинг, облегчит внедрение общей логики обработки ошибок и т.п.
С помощью декораторов можно красиво объявлять классы TypeScript, которые будут соответствовать контроллерам на сервере. Объявление методов при этом мы постараемся максимально упростить, так чтобы они содержали только одну строчку, а url будем формировать на основе информации из декораторов.
var ControllerNameMetadataKey = "Habr_PFight77_ControllerName";
// Первый декоратор.
// К сожалению, нет надежного способа узнать
// имя класса (устойчивого к минификации),
// поэтому имя класса придется передавать вручную.
function Controller(name: string) {
return (target: Function) {
Reflect.defineMetadata(ControllerNameMetadataKey, name, target.prototype);
};
}
// Второй декоратор, применяемый к методам
function Action(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> {
// Запоминаем исходную функцию
var originalMethod = descriptor.value;
// Подменяем ее на нашу обертку
descriptor.value = function ActionWrapper () {
// Получаем url, сохраненное декоратором Controller
var controllerName = Reflect.getMetadata(ControllerNameMetadataKey, target);
// Формируем url вида /ControllerName/ActionName
var url = "/" + controllerName + "/" + propertyKey;
// Передаем url последним параметром
[].push.call(arguments, url);
// Вызываем исходный метод с дополнительным параметром
originalMethod.apply(this, arguments);
};
// Обновляем дескриптор
return descriptor;
}
// Функция, упрощающая объявление методов
function post(data: any, args: IArguments): any {
// Получаем url, переданный декоратором @Action
var url = args[args.length - 1];
return $.ajax({ url: url, data: data, method: "POST" });
}
@Controller("Account")
class AccountController {
@Action
public Login(data: any): any {
return post(data, arguments);
}
}
var Account = new AccountController();
Account.Login({ username: "user", password: "111"});
Результат выполнения:
» Попробовать в Plunker [45]
» Посмотреть в Playground [46]
Можно также добавить декораторы параметров так, чтобы сигнатура метода в TypeScript полностью повторяла сигнатуру серверного метода. С помощью декораторов можно сохранять имя каждого параметра и при выполнении запроса формировать на основе этих данных соответствующий JSON. К сожалению, получить имя параметра в коде декораторы не позволяют, поэтому придется передавать имя в декоратор вручную (так же, как в декоратор Controller [47]).
Декораторы в TypeScript являются настолько мощным инструментом, что буквально позволяют расширять язык новой функциональностью. В этом плане они даже чем-то напоминают препроцессор в С++, с помощью которого можно здорово облагородить или запутать свой код.
Как было продемонстрировано в статье, в нашем распоряжении следующий ряд приемов:
Модификация дескриптора метода или свойства. В частности, можно подменить метод оберткой, задать дескриптор для поля, с объявлением методов доступа и т.д. В целом, из декоратора можно произвести любую трансформацию прототипа класса.
Сохранение и использование метаданных при помощи класса Reflect. Мы можем передать в декоратор любое значение, также нам доступно имя свойства или метода, устойчивое к минифкации.
Использование этих приемов может быть самым разнообразным, в зависимости от конкретных нужд. Например, в Легком Клиенте 8 мы активно используем декораторы для объявления сервисов взаимодействия с сервером. Наша реализация чуть сложнее представленной в статье (мы используем декораторы параметров), но в целом построена по тому же принципу. Кроме того, мы думаем еще задействовать несколько декораторов для объявления публичного API наших ReactJS компонентов, а также автоматизировать привязку обработчиков событий к this [48].
На этом пока все. Пишите впечатления в комментариях, делитесь своим опытом использования декораторов.
UPD. Как заметил whileTrue [49], в ES7 декораторы не вошли. Будем надеяться, что хотя бы в ES8 попадут.
Автор: ДоксВижн
Источник [50]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/200527
Ссылки в тексте:
[1] заявлены: https://github.com/tc39/proposal-decorators
[2] объектной модели JavaScript: https://habrahabr.ru/company/enterra/blog/153365/
[3] доступны в Babel: https://habrahabr.ru/post/277021/
[4] называют также свойствами: http://www.typescriptlang.org/docs/handbook/decorators.html#decorators
[5] появляется: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09myField%3A%20number%3B%0D%0A%0D%0A%09get%20MyPrperty()%3A%20number%20%7B%0D%0A%09%09return%20this.myField%3B%0D%0A%09%7D%0D%0A%7D
[6] не появляется: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09myField%3A%20number%3B%0D%0A%7D
[7] разного в каждом случае: #decorator-types
[8] TypeScript Playground: http://www.typescriptlang.org/play/
[9] Декоратор класса: http://www.typescriptlang.org/docs/handbook/decorators.html#class-decorators
[10] Пример в playground: http://www.typescriptlang.org/play/index.html#src=%40MyClassDecorator%0D%0Aclass%20MyClass%20%7B%0D%0A%09constructor()%20%7B%0D%0A%09%09console.info('it%20is%20ctor')%3B%0D%0A%09%7D%0D%0A%7D%0D%0A%0D%0Afunction%20MyClassDecorator(target%3A%20Function)%3A%20any%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09return%20target%3B%0D%0A%7D)
[11] Декоратор метода: http://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators
[12] Пример в playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09%40MyMethodDecorator%0D%0A%09myMethod()%20%7B%0D%0A%09%7D%0D%0A%7D%0D%0A%0D%0Afunction%20MyMethodDecorator(target%3A%20Object%2C%20propertyKey%3A%20string%2C%20descriptor%3A%20TypedPropertyDescriptor%3Cany%3E)%3A%20TypedPropertyDescriptor%3Cany%3E%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09console.log(propertyKey)%3B%0D%0A%09console.log(descriptor)%3B%0D%0A%09return%20descriptor%3B%0D%0A%7D
[13] дескриптор: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
[14] *: #target-es3-bug
[15] Пример в playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09%40MyStaticMethodDecorator%0D%0A%09static%20myStaticMethod()%20%7B%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20MyStaticMethodDecorator(target%3A%20Object%2C%20propertyKey%3A%20string%2C%20descriptor%3A%20TypedPropertyDescriptor%3Cany%3E)%3A%20TypedPropertyDescriptor%3Cany%3E%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09console.log(propertyKey)%3B%0D%0A%09console.log(descriptor)%3B%0D%0A%09return%20descriptor%3B%0D%0A%7D
[16] Декоратор методов доступа: http://www.typescriptlang.org/docs/handbook/decorators.html#accessor-decorators
[17] Пример в playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09private%20mMyProperty%3A%20number%3B%0D%0A%0D%0A%09%40MyPropertyDecorator%0D%0A%09get%20myProperty()%3A%20number%20%7B%0D%0A%09%09return%20this.mMyProperty%3B%0D%0A%09%7D%0D%0A%09set%20myProperty(val%3A%20number)%20%7B%0D%0A%09%09this.mMyProperty%20%3D%20val%3B%0D%0A%09%7D%0D%0A%7D%0D%0A%0D%0Afunction%20MyPropertyDecorator(target%3A%20Object%2C%20propertyKey%3A%20string%2C%20descriptor%3A%20TypedPropertyDescriptor%3Cany%3E)%3A%20TypedPropertyDescriptor%3Cany%3E%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09console.log(propertyKey)%3B%0D%0A%09console.log(descriptor)%3B%0D%0A%09return%20descriptor%3B%0D%0A%7D
[18] Декоратор параметра: http://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators
[19] Пример в playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09myMethod(%0D%0A%09%09%40MyParamDecorator%20param1%3A%20number%2C%0D%0A%09%09%40MyParamDecorator%20param2%3A%20number)%20%7B%0D%0A%09%7D%0D%0A%7D%0D%0A%0D%0Afunction%20MyParamDecorator(target%3A%20Object%2C%20propertyKey%3A%20string%2C%20index%3A%20number)%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09console.log(propertyKey)%3B%0D%0A%09console.log(index)%3B%0D%0A%7D
[20] Декоратор поля (свойства): http://www.typescriptlang.org/docs/handbook/decorators.html#property-decorators
[21] Пример в playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09%40MyFieldDecorator%0D%0A%09myField%3A%20number%3B%0D%0A%7D%0D%0A%0D%0Afunction%20MyFieldDecorator(target%3A%20Object%2C%20propertyKey%3A%20string)%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09console.log(propertyKey)%3B%0D%0A%7D
[22] баг в reflect-metadata: https://github.com/rbuckton/ReflectDecorators/issues/48
[23] Пример в playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09%40MyFieldDecorator%0D%0A%09static%20myField%3A%20number%3B%0D%0A%7D%0D%0A%0D%0Afunction%20MyFieldDecorator(target%3A%20Function%2C%20propertyKey%3A%20string)%20%7B%0D%0A%09console.log(target)%3B%0D%0A%09console.log(propertyKey)%3B%0D%0A%7D
[24] TypeScript 2.0: http://www.typescriptlang.org/docs/release-notes/typescript-2.0.html
[25] это баг в компиляторе TypeScript: https://github.com/Microsoft/TypeScript/issues/11658
[26] баг в JSFiddle: https://github.com/jsfiddle/jsfiddle-issues/issues/906
[27] реализация декоратора @safe: https://jsfiddle.net/PFight/msac244r/
[28] Reflect: https://www.npmjs.com/package/reflect-metadata#api
[29] reflect-metadata: https://github.com/rbuckton/ReflectDecorators
[30] typings: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/reflect-metadata
[31] Попробовать в действии в Plunker: https://embed.plnkr.co/hJIvnIYCdmnvJlUZZUCw/
[32] Посмотреть в Playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09%0D%0A%09%40safe%20public%20foo(str%3A%20string%2C%20data%3A%20any)%3A%20boolean%20%7B%0D%0A%20%20%20%20return%20str.length%20%3E%200%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20safe(target%3A%20Object%2C%20propertyKey%3A%20string)%20%7B%0D%0A%20%20var%20descriptor%20%3D%20Object.getOwnPropertyDescriptor(target%2C%20propertyKey)%3B%0D%0A%20%20var%20originalMethod%20%3D%20descriptor.value%3B%0D%0A%20%20descriptor.value%20%3D%20function%20NoErrorWrapper%20()%20%7B%0D%0A%20%20%20%20try%20%7B%0D%0A%20%20%20%20%20%20originalMethod.apply(this%2C%20arguments)%3B%0D%0A%20%20%20%20%7D%20catch(ex)%20%7B%0D%0A%20%20%20%20%20%20var%20message%20%3D%20ex.name%20%2B%20%22%20occured%20in%20call%20of%20%22%20%2B%20%0D%0A%20%20%20%20%20%20%20%20%20%20target.constructor.name%20%2B%20%22.%22%20%2B%20propertyKey%20%2B%20%22%3A%20%22%20%2B%20ex.message%20%2B%20%22%5Cn%22%3B%0D%0A%20%20%20%20%20%20message%20%2B%3D%20%22Arguments%3A%5Cn%22%3B%0D%0A%20%20%20%20%20%20%5B%5D.forEach.call(arguments%2C%20(x%2C%20i)%20%3D%3E%20%0D%0A%20%20%20%20%20%20%20%20%20message%20%2B%3D%20%22%20%20%20%5B%22%20%2B%20i%20%2B%20%22%5D%3A%20%22%20%2B%20JSON.stringify(x)%20%2B%20%22%20%5Cn%22)%3B%0D%0A%20%20%20%20%20%20message%20%2B%3D%20%22Callstack%3A%22%20%2B%20ex.stack%3B%0D%0A%20%20%20%20%20%20console.error(message)%3B%0D%0A%20%20%20%20%20%20document.body.innerHTML%20%2B%3D%20%22%3Cbr%3E%3Cpre%20style%3D'color%3A%20red'%3E%22%20%2B%20message%20%2B%20%22%3C%2Fpre%3E%22%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%3B%0D%0A%20%20Object.defineProperty(target%2C%20propertyKey%2C%20descriptor)%3B%0D%0A%20%20return%20descriptor%3B%0D%0A%7D%0D%0A%0D%0Aconsole.info(%22Starting...%22)%3B%0D%0Adocument.body.innerHTML%20%2B%3D%20%22Starting...%3Cbr%3E%22%3B%0D%0Avar%20my%20%3D%20new%20MyClass()%3B%0D%0Amy.foo(null%2C%20%7B%20answer%3A%2042%20%7D)%3B%0D%0Aconsole.info(%22Continue%20execution%22)%3B%0D%0Adocument.body.innerHTML%20%2B%3D%20%22%3Cbr%3EContinue%20execution%22%3B
[33] Попробовать в действии в Plunker: https://embed.plnkr.co/7k6FZqoB9gYHm0yYqiXC/
[34] Посмотреть в Playground: http://www.typescriptlang.org/play/index.html#src=class%20MyClass%20%7B%0D%0A%09%40OnChange(MyClass.onFieldChange)%0D%0A%09public%20mMyField%3A%20number%20%3D%2042%3B%0D%0A%0D%0A%09static%20onFieldChange(self%3A%20MyClass%2C%20newVal%3A%20number)%3A%20void%20%7B%0D%0A%20%20%20%20document.body.innerHTML%20%2B%3D%0D%0A%20%20%20%20%20%20%22%3Cli%3EChanging%20from%20%22%20%2B%20self.mMyField%20%2B%20%22%20to%20%22%20%2B%20newVal%20%2B%20%22%3C%2Fli%3E%22%3B%0D%0A%09%7D%0D%0A%7D%0D%0A%0D%0Afunction%20OnChange%3CClassT%2C%20T%3E(callback%3A%20(ClassT%2C%20T)%20%3D%3E%20void)%3A%20any%20%7B%0D%0A%20%20%20%20return%20(target%3A%20Object%2C%20propertyKey%3A%20string%20%7C%20symbol)%20%3D%3E%20%7B%0D%0A%20%20%20%20%20%20%2F%2F%20%D0%9D%D0%B5%D0%BE%D0%B1%D1%85%D0%BE%D0%B4%D0%B8%D0%BC%D0%BE%20%D0%B7%D0%B0%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D1%83%D1%89%D0%B5%D1%81%D1%82%D0%B2%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D0%B4%D0%B5%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D1%80%2C%20%D0%B5%D1%81%D0%BB%D0%B8%20%D0%BE%D0%BD%20%D0%B5%D1%81%D1%82%D1%8C.%0D%0A%20%20%20%20%20%20%2F%2F%20%D0%AD%D1%82%D0%BE%20%D0%BF%D0%BE%D0%B7%D0%B2%D0%BE%D0%BB%D0%B8%D1%82%20%D0%BE%D0%B1%D1%8A%D1%8F%D0%B2%D1%8F%D1%82%D1%8C%20%D0%BD%D0%B5%D1%81%D0%BA%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE%20%D0%B4%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%BE%D0%B2%20%D0%BD%D0%B0%20%D0%BE%D0%B4%D0%BD%D0%BE%D0%BC%20%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B5.%0D%0A%20%20%20%20%20%20var%20descriptor%20%3D%20Object.getOwnPropertyDescriptor(target%2C%20propertyKey)%20%0D%0A%20%20%20%20%20%20%20%20%7C%7C%20%7Bconfigurable%3A%20true%2C%20enumerable%3A%20true%7D%3B%0D%0A%20%20%20%20%20%20%2F%2F%20%D0%9F%D0%BE%D0%B4%D0%BC%D0%B5%D0%BD%D1%8F%D0%B5%D0%BC%20%D0%B8%D0%BB%D0%B8%20%D0%BE%D0%B1%D1%8A%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D0%BC%20get%20%D0%B8%20set%0D%0A%20%20%20%20%20%20var%20value%3A%20T%3B%0D%0A%20%20%20%20%20%20var%20originalGet%20%3D%20descriptor.get%20%7C%7C%20(()%20%3D%3E%20value)%3B%0D%0A%20%20%20%20%20%20var%20originalSet%20%3D%20descriptor.set%20%7C%7C%20(val%20%3D%3E%20value%20%3D%20val)%3B%0D%0A%20%20%20%20%20%20descriptor.get%20%3D%20originalGet%3B%0D%0A%20%20%20%20%20%20descriptor.set%20%3D%20function(newVal%3A%20T)%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20%D0%92%D0%BD%D0%B8%D0%BC%D0%B0%D0%BD%D0%B8%D0%B5%2C%20%D0%B5%D1%81%D0%BB%D0%B8%20%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%8F%D0%B5%D0%BC%20set%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20function%2C%20%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20%D1%82%D0%BE%20this%20-%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D1%8D%D0%BA%D0%B7%D0%B5%D0%BC%D0%BF%D0%BB%D1%8F%D1%80%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0%2C%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20%D0%B5%D1%81%D0%BB%D0%B8%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D0%BB%D1%8F%D0%BC%D0%B1%D0%B4%D1%83%2C%20%D1%82%D0%BE%20this%20-%20Window!!!%0D%0A%20%20%20%20%20%20%20%20var%20currentVal%20%3D%20originalGet.call(this)%3B%0D%0A%20%20%20%20%20%20%20%20if%20(newVal%20!%3D%20currentVal)%20%7B%0D%0A%20%20%20%20%20%20%20%20%09%2F%2F%20%D0%92%D1%8B%D0%B7%D1%8B%D0%B2%D0%B0%D0%B5%D0%BC%20%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%20callback%20%D1%81%20%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%20%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%B0%D0%BC%D0%B8%0D%0A%20%20%20%20%20%20%20%20%20%20callback.call(target.constructor%2C%20this%2C%20newVal)%3B%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%20%20%20%20originalSet.call(this%2C%20newVal)%3B%0D%0A%20%20%20%20%20%20%7D%3B%0D%0A%20%20%20%20%20%20%2F%2F%20%D0%9E%D0%B1%D1%8A%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D0%BC%20%D0%BD%D0%BE%D0%B2%D0%BE%D0%B5%20%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%BE%2C%20%D0%BB%D0%B8%D0%B1%D0%BE%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D1%8F%D0%B5%D0%BC%20%D0%B4%D0%B5%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D1%80%0D%0A%20%20%20%20%20%20Object.defineProperty(target%2C%20propertyKey%2C%20descriptor)%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Avar%20my%20%3D%20new%20MyClass()%3B%0D%0Amy.mMyField%20%3D%2043%3B%0D%0Amy.mMyField%20%3D%2044%3B
[35] альтернативный вариант со строковым параметром: https://jsfiddle.net/PFight/p7ubjx6c
[36] с использованием лямбды: https://jsfiddle.net/PFight/525kjkgp/1/
[37] Попробовать в Plunker: https://embed.plnkr.co/fcjDyqdK6kG9yPWlTsNB/
[38] Посмотреть в Playground: http://www.typescriptlang.org/play/index.html#src=%0D%0A%0D%0Afunction%20Inject(target%3A%20Object%2C%20propKey%3A%20string)%3A%20any%20%7B%0D%0A%09%09var%20propType%20%3D%20Reflect.getMetadata(%22design%3Atype%22%2C%20target%2C%20propKey)%3B%0D%0A%20%20%20%09console.log(propType)%3B%0D%0A%20%20%20%20var%20descriptor%20%3D%20%7B%0D%0A%20%20%20%20%09get%3A%20function%20()%20%7B%20%0D%0A%20%20%20%20%09%20%20var%20serviceLocator%20%3D%20this.serviceLocator%20%7C%7C%20globalSericeLocator%3B%0D%0A%20%20%20%20%09%20%20return%20%20serviceLocator.getService(propType)%3B%20%20%0D%0A%20%20%20%20%09%20%20%0D%0A%20%20%20%20%09%7D%0D%0A%20%20%20%20%7D%3B%0D%0A%20%20%20%20Object.defineProperty(target%2C%20propKey%2C%20descriptor)%3B%0D%0A%20%20%20%20return%20descriptor%3B%0D%0A%7D%0D%0A%0D%0Aclass%20MyClass%20%7B%0D%0A%09%40Inject%0D%0A%20%20private%20logService%3A%20ILogService%3B%0D%0A%20%20%0D%0A%20%20sayHello()%20%7B%0D%0A%20%20%09this.logService.log(%22Hello%20there%22)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0A%0D%0Aabstract%20class%20ILogService%20%7B%0D%0A%09abstract%20log(msg%3A%20string)%3A%20void%3B%0D%0A%7D%0D%0A%0D%0Aclass%20ServiceLocator%20%7B%0D%0A%20%20services%3A%20%5B%7BinterfaceType%3A%20Function%2C%20instance%3A%20Object%20%7D%5D%20%3D%20%5B%5D%20as%20any%3B%0D%0A%0D%0A%20%20registerService(interfaceType%3A%20Function%2C%20instance%3A%20Object)%20%7B%0D%0A%20%20%20%20var%20record%20%3D%20this.services.find(x%20%3D%3E%20x.interfaceType%20%3D%3D%20interfaceType)%3B%0D%0A%20%20%20%20if%20(!record)%20%7B%0D%0A%20%20%20%20%20%20record%20%3D%20%7B%20interfaceType%3A%20interfaceType%2C%20instance%3A%20instance%7D%3B%0D%0A%20%20%20%20%20%20this.services.push(record)%3B%0D%0A%20%20%20%20%7D%20else%20%7B%0D%0A%20%20%20%20%20%20record.instance%20%3D%20instance%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%20%20getService(interfaceType%3A%20Function)%20%7B%0D%0A%20%20%20%20return%20this.services.find(x%20%3D%3E%20x.interfaceType%20%3D%3D%20interfaceType).instance%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Avar%20globalSericeLocator%20%3D%20new%20ServiceLocator()%3B
[39] Попробовать в Plunker: https://embed.plnkr.co/q0PJWcLMnMUepaDbRNjb/
[40] Посмотреть в Playground: https://www.typescriptlang.org/play/index.html#src=%2F%2F%20%D0%A3%D0%BD%D0%B8%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%BA%D0%BB%D1%8E%D1%87%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D1%88%D0%B8%D1%85%20%D0%BC%D0%B5%D1%82%D0%B0%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%0D%0Aconst%20JsonNameMetadataKey%20%3D%20%22Habrahabr_PFight77_JsonName%22%3B%0D%0A%2F%2F%20%D0%94%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80%0D%0Afunction%20JsonName(name%3A%20string)%20%7B%0D%0A%20%20%20%20return%20(target%3A%20Object%2C%20propertyKey%3A%20string)%20%3D%3E%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D1%8F%D0%B5%D0%BC%20%D0%B2%20%D0%BC%D0%B5%D1%82%D0%B0%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%D0%BD%D0%BD%D1%8B%D0%B9%20name%0D%0A%20%20%20%20%20%20%20%20Reflect.defineMetadata(JsonNameMetadataKey%2C%20name%2C%20target%2C%20propertyKey)%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0Afunction%20serialize(model%3A%20Object)%3A%20string%20%7B%0D%0A%09var%20result%20%3D%20%7B%7D%3B%0D%0A%20%20var%20target%20%3D%20Object.getPrototypeOf(model)%3B%0D%0A%20%20for(var%20prop%20in%20model)%20%7B%0D%0A%20%20%09%2F%2F%20%D0%97%D0%B0%D0%B3%D1%80%D1%83%D0%B6%D0%B0%D0%B5%D0%BC%20%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%BE%D0%BC%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%0D%0A%20%20%09var%20jsonName%20%3D%20Reflect.getMetadata(%0D%0A%20%20%20%20%09JsonNameMetadataKey%2C%20target%2C%20prop)%20%7C%7C%20prop%3B%20%20%20%20%0D%0A%20%20%20%20result%5BjsonName%5D%20%3D%20model%5Bprop%5D%3B%0D%0A%20%20%7D%0D%0A%20%20return%20JSON.stringify(result)%3B%0D%0A%7D%0D%0A%0D%0Aclass%20Model%20%7B%0D%0A%20%20%40JsonName(%22name%22)%0D%0A%20%20public%20title%3A%20string%3B%0D%0A%20%20public%20description%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Avar%20model%20%3D%20new%20Model()%3B%0D%0Amodel.title%20%3D%20%22Hello%20there%22%3B%0D%0Amodel.description%20%3D%20%22Decorators%20are%20cool!%22%0D%0Avar%20json%20%3D%20serialize(model)%3B%0D%0Adocument.body.innerHTML%20%2B%3D%20%22%3Ch3%3EModel%3A%3C%2Fh3%3E%3Cp%3E%3Cpre%3E%22%20%2B%20JSON.stringify(model)%20%2B%20%22%3C%2Fpre%3E%3C%2Fp%3E%22%3B%0D%0Adocument.body.innerHTML%20%2B%3D%20%22%3Ch3%3EResult%20json%3A%3C%2Fh3%3E%3Cp%3E%3Cpre%3E%22%20%2B%20json%20%2B%20%22%3C%2Fpre%3E%3C%2Fp%3E%22%3B%0D%0A
[41] декоратор поля: #field-decorator
[42] Object.getPrototypeOf: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
[43] посмотреть в plunker: http://embed.plnkr.co/MJLm7v
[44] Action: https://habrahabr.ru/users/action/
[45] Попробовать в Plunker: https://embed.plnkr.co/vLwbHJOG2kWwEjbHDrLl/
[46] Посмотреть в Playground: https://www.typescriptlang.org/play/index.html#src=var%20ControllerNameMetadataKey%20%3D%20%22Habr_PFight77_ControllerName%22%3B%0D%0Afunction%20Controller(name%3A%20string)%20%7B%0D%0A%09return%20(target%3A%20Function)%20%7B%0D%0A%20%20%20%20Reflect.defineMetadata(ControllerNameMetadataKey%2C%20name%2C%20target.prototype)%3B%0D%0A%20%20%7D%3B%0D%0A%7D%0D%0Afunction%20Action(target%3A%20Object%2C%20propertyKey%3A%20string)%20%7B%20%20%0D%0A%20%20%2F%2F%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%BC%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D0%B4%D0%B5%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D1%80%20%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%B0%0D%0A%20%20var%20descriptor%20%3D%20Object.getOwnPropertyDescriptor(target%2C%20propertyKey)%3B%0D%0A%20%20%2F%2F%20%D0%97%D0%B0%D0%BF%D0%BE%D0%BC%D0%B8%D0%BD%D0%B0%D0%B5%D0%BC%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%83%D1%8E%20%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8E%0D%0A%20%20var%20originalMethod%20%3D%20descriptor.value%3B%0D%0A%20%20%2F%2F%20%D0%9F%D0%BE%D0%B4%D0%BC%D0%B5%D0%BD%D1%8F%D0%B5%D0%BC%20%D0%B5%D0%B5%20%D0%BD%D0%B0%20%D0%BD%D0%B0%D1%88%D1%83%20%D0%BE%D0%B1%D0%B5%D1%80%D1%82%D0%BA%D1%83%0D%0A%20%20descriptor.value%20%3D%20function%20ActionWrapper%20()%20%7B%0D%0A%20%20%09%09%2F%2F%20%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%BC%20url%2C%20%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5%20%D0%B4%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%BE%D0%BC%20Controller%20%0D%0A%20%20%20%20%20%20var%20controllerName%20%3D%20Reflect.getMetadata(ControllerNameMetadataKey%2C%20target)%3B%0D%0A%20%20%20%20%20%20var%20url%20%3D%20%22%2F%22%20%2B%20controllerName%20%2B%20%22%2F%22%20%2B%20propertyKey%3B%0D%0A%20%20%09%09%2F%2F%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D0%B5%D0%BC%20url%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%BC%20%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%BE%D0%BC%0D%0A%20%20%09%09%5B%5D.push.call(arguments%2C%20url)%3B%0D%0A%20%20%20%20%20%20%2F%2F%20%D0%92%D1%8B%D0%B7%D1%8B%D0%B2%D0%B0%D0%B5%D0%BC%20%D0%B8%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%0D%0A%20%20%20%20%20%20originalMethod.apply(this%2C%20arguments)%3B%0D%0A%20%20%7D%3B%0D%0A%20%20%2F%2F%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D1%8F%D0%B5%D0%BC%20%D0%B4%D0%B5%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D1%80%0D%0A%20%20Object.defineProperty(target%2C%20propertyKey%2C%20descriptor)%3B%0D%0A%20%20return%20descriptor%3B%0D%0A%7D%0D%0Afunction%20post(data%3A%20any%2C%20args%3A%20IArguments)%3A%20any%20%7B%0D%0A%09var%20url%20%3D%20args%5Bargs.length%20-%201%5D%3B%0D%0A%20%20document.body.innerHTML%20%3D%20%22Performing%20request%20to%20url%3A%20%22%20%2B%20url%20%2B%20%0D%0A%20%20%09%22%20%3Cbr%3EData%3A%20%22%20%2B%20JSON.stringify(data)%3B%0D%0A%20%20return%20%24.ajax(%7B%20url%3A%20url%2C%20data%3A%20data%2C%20method%3A%20%22POST%22%20%7D)%3B%0D%0A%7D%0D%0A%0D%0A%40Controller(%22Account%22)%0D%0Aclass%20AccountController%20%7B%0D%0A%09%40Action%0D%0A%20%20public%20Login(data%3A%20any)%3A%20any%20%7B%0D%0A%20%20%20%20return%20post(data%2C%20arguments)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0Avar%20Account%20%3D%20new%20AccountController()%3B%0D%0A%0D%0AAccount.Login(%7B%20username%3A%20%22user%22%2C%20password%3A%20%22111%22%7D)%3B
[47] Controller: https://habrahabr.ru/users/controller/
[48] привязку обработчиков событий к this: http://stackoverflow.com/questions/29732015/value-of-this-in-react-event-handler
[49] whileTrue: https://habrahabr.ru/users/whiletrue/
[50] Источник: https://habrahabr.ru/post/310870/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.