TypeScript 5.0引入了标准化的ECMAScript装饰器提案实现,配合reflect-metadata库的元数据反射能力,可以在类、方法、属性级别注入横切逻辑。前端开发中,装饰器模式配合TypeScript类型系统,能够以声明式方式实现依赖注入、参数校验、日志埋点、权限控制等通用功能,显著减少重复代码。
TypeScript 5.x装饰器语法与ECMAScript Stage 3提案
TypeScript 5.0开始支持ECMAScript Stage 3装饰器提案,与旧版实验性装饰器(experimentalDecorators)存在语法差异。新装饰器使用@符号声明,通过工厂函数返回元数据描述对象:
// tsconfig.json 配置
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": false,
"emitDecoratorMetadata": true,
"lib": ["ES2022", "DOM"]
}
}
// 装饰器工厂与上下文对象
function log(target, context) {
if (context.kind === 'method') {
return function(...args) {
console.log(`[LOG] 调用方法 ${String(context.name)}`);
const result = target.apply(this, args);
console.log(`[LOG] 方法 ${String(context.name)} 返回:`, result);
return result;
};
}
}
class UserService {
@log
getUser(id: number) {
return { id, name: '张三' };
}
}
同时安装reflect-metadata提供运行时类型信息反射:
npm install reflect-metadata
npm install -D @types/core-js
方法装饰器实现API请求参数自动校验
通过装饰器拦截方法调用,在执行前校验参数类型和范围:
import 'reflect-metadata';
function validate(target, context) {
if (context.kind !== 'method') return;
return function(...args) {
const paramTypes: any[] = Reflect.getMetadata(
'design:paramtypes',
target,
context.name
);
args.forEach((arg, index) => {
const expectedType = paramTypes[index];
const actualType = typeof arg;
const typeNameMap = {
'String': 'string',
'Number': 'number',
'Boolean': 'boolean',
'Object': 'object',
'Array': 'object'
};
const expectedTypeName = typeNameMap[expectedType.name] || 'object';
if (actualType !== expectedTypeName) {
throw new TypeError(
`参数 ${index + 1} 期望类型 ${expectedType.name},实际 ${actualType}`
);
}
});
return target.apply(this, args);
};
}
function paramRange(min: number, max: number) {
return function(target, context) {
if (context.kind !== 'method') return;
const originalMethod = target;
return function(value: number) {
if (value < min || value > max) {
throw new RangeError(
`参数值 ${value} 超出范围 [${min}, ${max}]`
);
}
return originalMethod.call(this, value);
};
};
}
class ProductService {
@validate
@paramRange(1, 100)
getPrice(discount: number): number {
return 100 * (1 - discount / 100);
}
}
类装饰器实现依赖注入容器集成
利用reflect-metadata存储类构造函数参数类型,实现简易DI容器:
import 'reflect-metadata';
const container = new Map();
function Injectable() {
return function(target, context) {
if (context.kind !== 'class') return;
const paramTypes: any[] = Reflect.getMetadata(
'design:paramtypes',
target
) || [];
const deps = paramTypes.map(paramType => {
const depInstance = container.get(paramType.name);
if (!depInstance) {
const instance = new paramType();
container.set(paramType.name, instance);
return instance;
}
return depInstance;
});
const instance = new target(...deps);
container.set(target.name, instance);
return target;
};
}
function Inject(token: string) {
return function(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
get: () => container.get(token),
enumerable: true,
configurable: true
});
};
}
@Injectable()
class DatabaseService {
query(sql: string) {
return [{ id: 1, name: 'record' }];
}
}
@Injectable()
class UserRepository {
constructor(private db: DatabaseService) {}
findById(id: number) {
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
@Injectable()
class UserService {
constructor(private repo: UserRepository) {}
getUser(id: number) {
return this.repo.findById(id);
}
}
const userService = container.get('UserService');
console.log(userService.getUser(1));
属性装饰器实现API路由自动注册
在Node.js后端控制器中,通过装饰器收集路由元数据实现自动注册:
import 'reflect-metadata';
const ROUTE_KEY = 'custom:routes';
function createRouteDecorator(method: string) {
return function(path: string) {
return function(target: any, context: ClassMethodDecoratorContext) {
const routes = Reflect.getMetadata(ROUTE_KEY, target.constructor) || [];
routes.push({
method,
path,
handler: context.name,
});
Reflect.defineMetadata(ROUTE_KEY, routes, target.constructor);
};
};
}
const Get = createRouteDecorator('GET');
const Post = createRouteDecorator('POST');
const Put = createRouteDecorator('PUT');
const Delete = createRouteDecorator('DELETE');
class RouteRegistrar {
static register(controllerClass: any, basePath: string) {
const routes = Reflect.getMetadata(ROUTE_KEY, controllerClass) || [];
const controller = new controllerClass();
return routes.map(route => ({
method: route.method,
path: basePath + route.path,
handler: controller[route.handler].bind(controller)
}));
}
}
class UserController {
@Get('/users')
listUsers() {
return { data: [{ id: 1, name: '张三' }] };
}
@Get('/users/:id')
getUser(id: string) {
return { data: { id: Number(id), name: '张三' } };
}
@Post('/users')
createUser(body: any) {
return { message: '创建成功', id: Date.now() };
}
@Put('/users/:id')
updateUser(id: string, body: any) {
return { message: '更新成功' };
}
@Delete('/users/:id')
deleteUser(id: string) {
return { message: '删除成功' };
}
}
const routes = RouteRegistrar.register(UserController, '/api/v1');
// 输出:
// { method: 'GET', path: '/api/v1/users', handler: [Function] }
// { method: 'GET', path: '/api/v1/users/:id', handler: [Function] }
// { method: 'POST', path: '/api/v1/users', handler: [Function] }
// { method: 'PUT', path: '/api/v1/users/:id', handler: [Function] }
// { method: 'DELETE', path: '/api/v1/users/:id', handler: [Function] }
TypeScript 5.x标准装饰器配合reflect-metadata,在前端开发中可覆盖依赖注入、参数校验、路由注册、缓存拦截、权限校验等场景。与实验性装饰器相比,新提案的类型推断更精确,上下文对象提供了更丰富的元信息,但部分旧库(如NestJS、TypeORM)仍依赖实验性装饰器,迁移时需注意兼容性。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript5x-zhuang-shi-qi-yu-yuan-shu-ju-fan-she-zai-ye-wu/