TypeScript Decorators
Decorators are an experimental feature of TypeScript.
They allow developers to add additional functionality to classes, methods, properties, or parameters without modifying the original class.
A decorator is essentially a function that can be called at runtime to modify the behavior of the target object.
SVG Diagram: Decorator TypesIntroduction to Decorators
Decorators use the `@` symbol as syntactic sugar and can be attached to classes, methods, accessors, properties, or parameters.
This pattern is commonly used in framework development; for example, Angular, TypeORM, etc., extensively use decorators to implement dependency injection, data validation, and other features.
Note:Decorators are currently an experimental feature and need to be explicitly enabled in tsconfig.json. When using them in production, please confirm the project's level of support for experimental features.
Configuration to Enable Decorators
Before using decorators, you need to enable the relevant compiler options in the TypeScript configuration file tsconfig.json.
tsconfig.json Configuration
"compilerOptions": {
// Enable decorator syntax
"experimentalDecorators": true,
// Enable decorator metadata (used for dependency injection frameworks)
"emitDecoratorMetadata": true
}
}
Parameter description:
- experimentalDecorators:Enables decorator syntax support; this is a prerequisite for using decorators.
- emitDecoratorMetadata:Generates decorator metadata in the compiled JavaScript for use by dependency injection frameworks.
Class Decorators
Class decorators are applied to the class constructor and can modify the class definition or add additional processing logic.
A class decorator receives one parameter: the constructor of the target class.
Basic Usage of Class Decorators
// The parameter target is the decorated class constructor
function sealed(target: Function) {
// Print the name of the class the decorator is applied to
console.log("Decorator applied to: " + target.name);
// Use Object.seal to lock the constructor and prototype
// Prevent adding or removing properties at runtime
Object.seal(target);
Object.seal(target.prototype);
}
// Use the @ syntax to apply the decorator to the class
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// Create an instance for testing
var person = new Person("EXAMPLE");
console.log("Created: " + person.name);
// Try to add a new property (will be prevented because the class is sealed)
// person.age = 25; // silently fails
Output:
装饰器 applied to: Person 创建: EXAMPLE
Explanation:
Class decorators execute at class definition time and are typically used to modify class behavior, add metadata, or implement AOP (Aspect-Oriented Programming).
Method Decorators
Method decorators are applied to class methods and can modify the method's Property Descriptor.
A method decorator receives three parameters: the target object, the property name, and the property descriptor.
Basic Usage of Method Decorators
// Return a decorator function
function enumerable(value: boolean) {
// Return a decorator function that receives three parameters
return function (
target: any, // The prototype object of the containing class
propertyKey: string, // The method name
descriptor: PropertyDescriptor // The property descriptor
) {
// Modify the enumerable attribute of the property
// false means the method is not enumerable
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
// Apply the decorator to set the method as non-enumerable
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
var g = new Greeter("World");
// Check whether the greet method is enumerable
console.log("Method enumerable: " + g.propertyIsEnumerable("greet"));
// Iterate over the object's properties
for (var key in g) {
console.log("Properties: " + key);
}
Output:
方法可枚举: false
Tip:PropertyDescriptor contains attributes such as enumerable, configurable, writable, and value, which can be modified as needed.
Accessor Decorators
Accessor decorators are applied to getter and setter methods of a class.
Similar to method decorators, accessor decorators can also modify the property descriptor.
Usage of Accessor Decorators
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// Modify the configurable attribute of the property
// false means the accessor cannot be reconfigured or deleted
descriptor.configurable = value;
};
}
class Point {
private _x: number = 0;
private _y: number = 0;
// Use the decorator to lock the getter
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
set x(value: number) {
this._x = value;
}
set y(value: number) {
this._y = value;
}
}
var point = new Point();
point.x = 10;
point.y = 20;
console.log("Coordinates: (" + point.x + ", " + point.y + ")");
Explanation:
Accessor decorators cannot be applied to both the getter and setter of the same property at the same time; only one of them can be chosen.
Property Decorators
Property decorators are applied to property definitions of a class.
A property decorator receives two parameters: the target object and the property name.
Usage of Property Decorators
function format(formatString: string) {
return function (
target: any, // The prototype object of the class
propertyKey: string // The property name
) {
// Store metadata on the target object
// Use propertyKey + "_format" as the key name to avoid conflicts
Object.defineProperty(target, propertyKey + "_format", {
value: formatString,
writable: false,
enumerable: false,
configurable: true
});
};
}
class User {
// Apply the property decorator to specify the date format
@format("YYYY-MM-DD")
birthDate: string;
constructor(birthDate: string) {
this.birthDate = birthDate;
}
}
var user = new User("1990-01-01");
console.log("Date of birth: " + user.birthDate);
// Access the stored metadata
console.log("Date format: " + (user as any).birthDate_format);
Output:
出生日期: 1990-01-01 日期格式: YYYY-MM-DD
Parameter Decorators
Parameter decorators are applied to parameters of class methods and can add metadata or markers to parameters.
A parameter decorator receives three parameters: the target object, the method name, and the index of the parameter in the function.
Usage of Parameter Decorators
// Used to record parameter information or perform validation
function logParameter(
target: any, // The prototype object of the class
propertyKey: string, // The method name
parameterIndex: number // The index position of the parameter in the function (starting from 0)
) {
console.log("Parameter decorator: " + propertyKey +
" " + (parameterIndex + 1) + "th parameter");
}
class Greeter {
greeting: string;
constructor(greeting: string) {
this.greeting = greeting;
}
// Use the @ syntax before the parameter to apply the decorator
greet(@logParameter name: string) {
return this.greeting + ", " + name;
}
}
var greeter = new Greeter("Hello");
greeter.greet("EXAMPLE");
Output:
参数装饰器: greet 第 1 个参数
Decorator Factory
A decorator factory is a function that returns a decorator function.
Through a decorator factory, you can pass custom parameters when applying the decorator, enabling more flexible configuration.
Decorator Factory for Colored Logs
function color(colorCode: string) {
// colorCode is the color code in ANSI escape sequences
// For example: 34 = blue, 31 = red, 32 = green
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// Save the original method
var originalMethod = descriptor.value;
// Override the method to add color
descriptor.value = function (...args: any[]) {
// Call the original method to get the return value
var result = originalMethod.apply(this, args);
// If in a terminal environment, add color to the output
// ANSI escape sequence format: \x1b[color code m content \x1b[0m
return "\x1b[" + colorCode + "m" + result + "\x1b[0m";
};
};
}
class Logger {
// Use decorator factory, pass in blue color code 34
@color("34")
log(message: string): string {
return message;
}
@color("31")
error(message: string): string {
return message;
}
@color("32")
success(message: string): string {
return message;
}
}
var logger = new Logger();
console.log(logger.log("This is a blue log"));
console.log(logger.error("This is a red error"));
console.log(logger.success("This is a green success"));
Run result:
This is blue log (displayed as blue in terminal) This is red error (displayed as red in terminal) This is green success (displayed as green in terminal)
Explanation:The decorator factory is the most commonly used form in actual development. It allows passing parameters when applying decorators, enabling configuration.
Execution Order of Decorators
When a class has multiple decorators, the execution order follows specific rules.
- Decorators are applied from bottom to top
- Multiple decorators of the same type execute from right to left
- Parameter decorators execute before method decorators
Example of Decorator Execution Order
function first() {
console.log("first decorator");
return function (target: any) {
console.log("first decorator function");
};
}
function second() {
console.log("second decorator");
return function (target: any) {
console.log("second decorator function");
};
}
@first()
@second()
class MyClass {
name: string;
}
var obj = new MyClass();
Run result:
second 装饰器 first 装饰器 second 装饰器函数 first 装饰器函数
Explanation:
The decorator function definitions execute first (console.log), then the decorator functions execute in order from bottom to top.
Real-world Application Scenarios
Decorators have a wide range of application scenarios in real projects.
Logging
Automatically record method call logs.
Example
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("Calling method: " + propertyKey + ", parameters: " + JSON.stringify(args));
var result = originalMethod.apply(this, args);
console.log("Method returned: " + JSON.stringify(result));
return result;
};
}
class MathService {
@log
add(a: number, b: number): number {
return a + b;
}
@log
multiply(a: number, b: number): number {
return a * b;
}
}
var math = new MathService();
console.log("Calculation result: " + math.add(5, 3));
Run result:
调用方法: add,参数: [5,3] 方法返回: 8 计算结果: 8
Permission Verification
Implement method-level permission checking.
Example
var currentUser = { role: "admin" };
// Permission decorator
function requireRole(role: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
if (currentUser.role !== role) {
console.log("Insufficient permissions, cannot execute " + propertyKey);
return null;
}
return originalMethod.apply(this, args);
};
};
}
class AdminService {
@requireRole("admin")
deleteUser(id: number): string {
return "Delete user " + id + " succeeded";
}
@requireRole("admin")
viewUser(id: number): string {
return "View user " + id;
}
}
var admin = new AdminService();
console.log(admin.viewUser(1));
console.log(admin.deleteUser(1));
// Simulate regular user
currentUser = { role: "user" };
console.log(admin.deleteUser(2));
Run result:
查看用户 1 删除用户 1 成功 权限不足,无法执行 deleteUser
Notes
- Experimental feature:Decorators are a _stage 3_ proposal of ECMAScript and are currently still an experimental feature
- Compiler options:Must enable experimentalDecorators
- Type definitions:A newer version of TypeScript is required for full type support
- Debugging note:Decorators execute at compile time, and some debugging tools may not correctly map source locations
Recommendation:When using decorators in a project, it is recommended to create dedicated decorator utility classes or function libraries to uniformly manage the definition and usage of decorators.
Summary
TypeScript decorators provide a powerful metaprogramming capability.
- Class decorators:Modify the class itself, can be used to add metadata, lock classes, etc.
- Method decorators:Modify method properties, can be used for logging, validation, etc.
- Accessor decorators:Modify getter/setter, control the configurability of properties
- Property decorators:Add metadata to properties
- Parameter decorators:Mark or validate method parameters
- Decorator factory:Achieve more flexible decorator configuration through parameterization
Other extensions