Appearance
TypeScript 装饰器
装饰器是 TypeScript 中的高级特性,允许我们以声明的方式修改类和类成员。装饰器使用 @expression 语法,其中 expression 必须计算为一个函数,在运行时调用。
装饰器基础
启用装饰器
首先需要在 tsconfig.json 中启用装饰器:
json
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
装饰器工厂
typescript
// 装饰器工厂函数
function color(value: string) { // 装饰器工厂
return function (target) { // 装饰器函数
// 操作 target
}
}
类装饰器
类装饰器应用于类的构造函数,用于观察、修改或替换类定义。
typescript
// 基本类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
// 带参数的类装饰器
function reportable<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
reportingURL = "http://www...";
};
}
@reportable
class Particles {
constructor(public position: number) {}
}
方法装饰器
方法装饰器应用于方法的属性描述符,用于观察、修改或替换方法定义。
typescript
// 方法装饰器定义
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Greeter2 {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
// 日志方法装饰器
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned`, result);
return result;
};
}
class Calculator {
@log
add(x: number, y: number): number {
return x + y;
}
}
访问器装饰器
访问器装饰器应用于访问器的属性描述符,用于观察、修改或替换访问器定义。
typescript
// 访问器装饰器
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { return this._y; }
}
属性装饰器
属性装饰器应用于属性声明,接收两个参数:构造函数和属性名。
typescript
// 属性装饰器
function format(target: Object, propertyKey: string) {
// 为属性创建元数据
Reflect.defineMetadata('format', 'default', target, propertyKey);
}
class Greeter3 {
@format
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// 更实用的属性装饰器示例
function minLength(length: number) {
return function(target: any, propertyKey: string) {
let value: string;
const getter = function() {
return value;
};
const setter = function(newVal: string) {
if (newVal.length < length) {
console.log(`Error: Length must be at least ${length} characters`);
} else {
value = newVal;
}
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class User {
@minLength(6)
password: string;
constructor(password: string) {
this.password = password;
}
}
参数装饰器
参数装饰器应用于方法参数,接收三个参数:构造函数、方法名和参数索引。
typescript
// 参数装饰器
function validate(target: Object, methodName: string, paramIndex: number) {
console.log(`Validating parameter at index ${paramIndex} for method ${methodName}`);
}
class Person {
name: string;
setName(@validate name: string) {
this.name = name;
}
}
// 更实用的参数装饰器示例
function inject(serviceName: string) {
return function(target: Object, methodName: string | symbol, paramIndex: number) {
// 存储依赖注入信息
const existingRequiredServices = Reflect.getMetadata('requiredServices', target, methodName) || [];
existingRequiredServices[paramIndex] = serviceName;
Reflect.defineMetadata('requiredServices', existingRequiredServices, target, methodName);
};
}
class UserService {
getUser(@inject('httpService') httpService: any, @inject('configService') configService: any) {
// 方法实现
}
}
高级装饰器模式
路由装饰器(类似 Express 或 Koa)
typescript
// 模拟路由装饰器
const routes: { path: string; method: string; handler: Function }[] = [];
function Route(path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET') {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
routes.push({
path,
method,
handler: descriptor.value
});
};
}
class UserController {
@Route('/users', 'GET')
getUsers() {
return [{ id: 1, name: 'John' }];
}
@Route('/users/:id', 'GET')
getUser() {
return { id: 1, name: 'John' };
}
@Route('/users', 'POST')
createUser() {
return { id: 2, name: 'Jane' };
}
}
依赖注入装饰器
typescript
// 简单的依赖注入系统
const injectableRegistry = new Map();
function Injectable() {
return function (constructor: Function) {
injectableRegistry.set(constructor.name, constructor);
};
}
function Inject(token: string) {
return function (target: any, propertyKey: string | symbol, parameterIndex: number) {
// 为参数存储注入标记
const existingTokens = Reflect.getMetadata('inject:tokens', target, propertyKey) || [];
existingTokens[parameterIndex] = token;
Reflect.defineMetadata('inject:tokens', existingTokens, target, propertyKey);
};
}
@Injectable()
class DatabaseService {
connect() {
console.log('Connected to database');
}
}
@Injectable()
class UserService {
constructor(@Inject('DatabaseService') private db: DatabaseService) {}
getUser(id: number) {
this.db.connect();
return { id, name: 'John' };
}
}
性能监控装饰器
typescript
function Performance(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey} executed in ${end - start} milliseconds`);
return result;
};
return descriptor;
}
class DataProcessor {
@Performance
processData(data: number[]): number[] {
return data.map(n => n * 2);
}
}
装饰器元数据
需要安装 reflect-metadata 包来使用装饰器元数据:
bash
npm install reflect-metadata
typescript
import 'reflect-metadata';
function Metadata(key: string, value: any) {
return function(target: Object, propertyKey: string | symbol) {
Reflect.defineMetadata(key, value, target, propertyKey);
};
}
class ExampleClass {
@Metadata('description', 'This is a sample property')
sampleProperty: string;
@Metadata('version', '1.0.0')
sampleMethod() {
// 方法实现
}
}
// 获取元数据
const description = Reflect.getMetadata('description', ExampleClass.prototype, 'sampleProperty');
console.log(description); // 输出: This is a sample property
装饰器实际应用
NestJS 风格的控制器装饰器
typescript
// 模拟 NestJS 风格的装饰器
function Controller(path: string) {
return function (constructor: Function) {
Reflect.defineMetadata('controller:path', path, constructor);
};
}
function Get(path: string = '') {
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('route:path', path, target, propertyKey);
Reflect.defineMetadata('route:method', 'GET', target, propertyKey);
};
}
function Post(path: string = '') {
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('route:path', path, target, propertyKey);
Reflect.defineMetadata('route:method', 'POST', target, propertyKey);
};
}
@Controller('/users')
class UsersController {
@Get('/')
findAll() {
return [];
}
@Get('/:id')
findOne() {
return {};
}
@Post('/')
create() {
return {};
}
}
小结
装饰器是 TypeScript 中一个强大的特性,允许我们以声明的方式修改类和类成员。通过装饰器,我们可以实现横切关注点(如日志、验证、授权等),使代码更加模块化和可重用。虽然装饰器目前仍处于提案阶段,但它们在许多框架(如 Angular、NestJS)中得到了广泛应用。