Декораторы являются инструментом декларативного программирования, они позволяют добавить к классам и их членам метаданные и тем самым изменить их поведение без изменения их кода.
Декораторы представляют функции, которые могут применяться к классам, методам, методом доступа (геттерам и сеттерам), свойствам, параметрам.
На текущий момент декораторы являются экпериментальной функциональностью языка TypeScript, поэтому при компиляции следует указывать параметр experimentalDecorators. Например, через файл tsconfig.json:
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
Либо через параметры в командной строке:
tsc app.ts -t ES5 --experimentalDecorators
Декоратор класса применяется к конструктору класса и позволяет изменять или заменять определение класса.
Декоратор класса представляет функцию, которая принимает один параметр:
function classDecoratorFn(constructor: Function){ }
В качестве параметра выступает конструктор класса. Например, определим простейший декоратор:
function sealed(constructor: Function) { console.log("sealed decorator"); Object.seal(constructor); Object.seal(constructor.prototype); } @sealed class User { name: string; constructor(name: string){ this.name = name; } print():void{ console.log(this.name); } }
Декоратор sealed с помощью функции Object.seal запрещает расширение прототипа класса User.
Для применения декоратора используется знак @. Сам декоратор ставится перед названием класса. То есть из-за применения декоратора мы, к примеру, не сможем добавить в класс User новое свойство следующим образом:
Object.defineProperty(User, 'age', { value: 17 });
Также декораторы могут изменять результат работы конструктора. В этом случае определение функции декоратора немного меняется, но она также в качестве параметра принимает конструктор класса:
function logger<TFunction extends Function>(target: TFunction): TFunction{ let newConstructor: Function = function(name:string){ console.log("Creating new instance"); this.name = name; this.age = 23; this.print = function():void{ console.log(this.name, this.age); } } return <TFunction>newConstructor; } @logger class User { name: string; constructor(name: string){ this.name = name; } print():void{ console.log(this.name); } } let tom = new User("Tom"); let bob = new User("Bob"); tom.print(); bob.print();
В данном случае декоратор logger типизирован типом TFunction, который является расширением типа Function, то есть функции. По сути это тип функции конструктора.
В самом декораторе передаваемый конструктор target никак не используется. Но создается новый конструктор. Мы предполагаем, что в конструктор будет
передаваться некоторый параметр, который будет называться name. Значение этого параметра передается свойству this.name = name;
.
Также в конструкторе устанавливается новое свойство this.age
и метод this.print()
, который выводит на консоль значения обоих свойств.
Далее декоратор применяется к классу User. У этого класса определен конструктор, который устанавливает свойство name. Однако поскольку мы переопределили конструктор, то в реальности при создании объекта User будет устанавливаться как свойство name, так и свойство age. И, кроме того, будет переопределяться метод print.
Вывод консоли браузера
Следует учитывать, что замена конструктора приводит к полной замене всех свойств и методов класса.