TypeScript 5 装饰器与元编程:从原理到依赖注入框架的完整实现
前端工程(更新于 2026年6月2日)
装饰器的前世今生
Stage 2(旧版,TypeScript 4)
// 旧版:装饰器是一个函数,接收 (target, key, descriptor)
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key} with`, args);
return original.apply(this, args);
};
}
Stage 3(新版,TypeScript 5+)
// 新版:装饰器是一个函数,返回一个函数
function Log(
value: Function,
context: ClassMethodDecoratorContext
) {
return function (this: any, ...args: any[]) {
console.log(`Calling ${String(context.name)} with`, args);
return value.apply(this, args);
};
}
关键差异
| 维度 | Stage 2(旧) | Stage 3(新) |
|---|---|---|
| 参数 | (target, key, descriptor) |
(value, context) |
| 返回值 | 修改 descriptor | 返回替换值 |
| 元数据 | 需要 reflect-metadata |
原生 context.metadata |
| 类装饰器 | 修改构造函数 | 返回新构造函数 |
| 执行顺序 | 从内到外 | 从外到内 |
| TC39 进度 | 废弃 | Stage 3 |
五种装饰器类型
1. 类装饰器
function sealed<T extends { new(...args: any[]): {} }>(
target: T,
context: ClassDecoratorContext
) {
Object.seal(target);
Object.seal(target.prototype);
return target;
}
@sealed
class Config {
static apiKey = 'xxx';
}
2. 方法装饰器
function debounce(delay: number) {
return function (
originalMethod: Function,
context: ClassMethodDecoratorContext
) {
let timer: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
clearTimeout(timer);
timer = setTimeout(() => originalMethod.apply(this, args), delay);
};
};
}
class SearchBox {
@debounce(300)
onInput(text: string) {
fetchSuggestions(text);
}
}
3. 属性装饰器
function required(
value: undefined,
context: ClassFieldDecoratorContext
) {
return function (this: any, initialValue: any) {
if (initialValue === undefined || initialValue === null) {
throw new Error(`${String(context.name)} is required`);
}
return initialValue;
};
}
class User {
@required name!: string;
@required email!: string;
}
4. Getter/Setter 装饰器
function format(template: string) {
return function (
target: Function,
context: ClassGetterDecoratorContext
) {
return function (this: any) {
const raw = target.call(this);
return template.replace('{}', String(raw));
};
};
}
class Product {
#price: number;
@format('${}')
get price() { return this.#price; }
}
5. 自动访问器装饰器(Auto-Accessor)
function logged(
target: ClassAccessorDecoratorTarget<any, any>,
context: ClassAccessorDecoratorContext
): ClassAccessorDecoratorResult<any, any> {
return {
get(this: any) {
const value = target.get.call(this);
console.log(`Getting ${String(context.name)}: ${value}`);
return value;
},
set(this: any, newValue: any) {
console.log(`Setting ${String(context.name)}: ${newValue}`);
target.set.call(this, newValue);
},
};
}
class Settings {
@logged accessor theme = 'light';
}
元数据反射
reflect-metadata
import 'reflect-metadata';
// 定义元数据
Reflect.defineMetadata('role', 'admin', UserController);
Reflect.defineMetadata('route', '/api/users', UserController.prototype, 'getUsers');
// 读取元数据
const role = Reflect.getMetadata('role', UserController); // 'admin'
const route = Reflect.getMetadata('route', UserController.prototype, 'getUsers'); // '/api/users'
装饰器 + 元数据 = 声明式编程
import 'reflect-metadata';
const ROUTE_KEY = Symbol('route');
const METHOD_KEY = Symbol('method');
function Get(path: string) {
return function (target: Function, context: ClassMethodDecoratorContext) {
Reflect.defineMetadata(ROUTE_KEY, path, context.static);
Reflect.defineMetadata(METHOD_KEY, 'GET', context.static);
};
}
function Controller(prefix: string) {
return function (target: Function, context: ClassDecoratorContext) {
Reflect.defineMetadata('prefix', prefix, target);
};
}
@Controller('/api/users')
class UserController {
@Get('/')
list() { return [{ id: 1, name: 'Alice' }]; }
@Get('/:id')
detail() { return { id: 1, name: 'Alice' }; }
}
// 自动注册路由
function registerRoutes(controller: any) {
const prefix = Reflect.getMetadata('prefix', controller.constructor);
const methods = Object.getOwnPropertyNames(controller.constructor.prototype);
methods.forEach(method => {
const route = Reflect.getMetadata(ROUTE_KEY, controller.constructor.prototype, method);
if (route) {
const fullPath = prefix + route;
console.log(`Register: GET ${fullPath} → ${method}`);
}
});
}
实战:从零实现 IoC 容器
import 'reflect-metadata';
const INJECT_KEY = Symbol('inject');
// @Injectable 装饰器:标记可注入的服务
function Injectable(target: Function, context: ClassDecoratorContext) {
Reflect.defineMetadata('injectable', true, target);
return target;
}
// @Inject 装饰器:声明依赖
function Inject(token: string) {
return function (value: undefined, context: ClassFieldDecoratorContext) {
return function (this: any) {
return Container.resolve(token);
};
};
}
// IoC 容器
class Container {
private static instances = new Map<string, any>();
private static registrations = new Map<string, Function>();
static register(token: string, target: Function) {
this.registrations.set(token, target);
}
static resolve<T>(token: string): T {
if (this.instances.has(token)) {
return this.instances.get(token);
}
const Target = this.registrations.get(token);
if (!Target) throw new Error(`No registration for ${token}`);
const instance = new (Target as any)();
this.instances.set(token, instance);
return instance;
}
}
// 使用
@Injectable
class Logger {
log(msg: string) { console.log(`[LOG] ${msg}`); }
}
@Injectable
class Database {
@Inject('Logger') logger!: Logger;
query(sql: string) {
this.logger.log(`Query: ${sql}`);
return [{ id: 1 }];
}
}
Container.register('Logger', Logger);
Container.register('Database', Database);
const db = Container.resolve<Database>('Database');
db.query('SELECT * FROM users');
实战:ORM 属性映射
import 'reflect-metadata';
const COLUMN_KEY = Symbol('column');
function Column(name: string, options?: { type?: string; primary?: boolean }) {
return function (value: undefined, context: ClassFieldDecoratorContext) {
Reflect.defineMetadata(COLUMN_KEY, { name, ...options }, context.static);
};
}
function Entity(table: string) {
return function (target: Function, context: ClassDecoratorContext) {
Reflect.defineMetadata('table', table, target);
};
}
@Entity('users')
class User {
@Column('id', { primary: true })
id!: number;
@Column('username')
name!: string;
@Column('created_at', { type: 'datetime' })
createdAt!: Date;
}
// 自动生成 SQL
function toSQL(entity: Function): string {
const table = Reflect.getMetadata('table', entity);
const fields: string[] = [];
const prototype = entity.prototype;
for (const key of Object.getOwnPropertyNames(prototype)) {
const col = Reflect.getMetadata(COLUMN_KEY, prototype, key);
if (col) {
fields.push(`${col.name} ${col.type || 'VARCHAR(255)'}`);
}
}
return `CREATE TABLE ${table} (${fields.join(', ')});`;
}
实战:验证框架
import 'reflect-metadata';
const VALIDATE_KEY = Symbol('validate');
function validate(validator: (value: any) => boolean, message: string) {
return function (value: undefined, context: ClassFieldDecoratorContext) {
const existing = Reflect.getMetadata(VALIDATE_KEY, context.static) || [];
existing.push({ validator, message, field: context.name });
Reflect.defineMetadata(VALIDATE_KEY, existing, context.static);
};
}
const Min = (n: number) => validate(v => v >= n, `Must be at least ${n}`);
const Max = (n: number) => validate(v => v <= n, `Must be at most ${n}`);
const Email = () => validate(v => /^[^@]+@[^@]+$/.test(v), 'Invalid email');
const Required = () => validate(v => v != null, 'Is required');
class CreateUserDTO {
@Required() @Min(1) id!: number;
@Required() @Email() email!: string;
@Required() @Min(0) @Max(150) age!: number;
}
function validateDto(instance: any): string[] {
const rules = Reflect.getMetadata(VALIDATE_KEY, instance.constructor) || [];
const errors: string[] = [];
for (const rule of rules) {
const value = instance[rule.field];
if (!rule.validator(value)) {
errors.push(`${String(rule.field)} ${rule.message}`);
}
}
return errors;
}
装饰器工厂模式
// 装饰器工厂:接收参数,返回装饰器
function Cache(ttl: number) {
const cache = new Map<string, { value: any; expiry: number }>();
return function (originalMethod: Function, context: ClassMethodDecoratorContext) {
return function (this: any, ...args: any[]) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() < cached.expiry) {
return cached.value;
}
const result = originalMethod.apply(this, args);
cache.set(key, { value: result, expiry: Date.now() + ttl * 1000 });
return result;
};
};
}
class ApiService {
@Cache(60) // 缓存 60 秒
async getUsers() {
return fetch('/api/users').then(r => r.json());
}
}
TypeScript 配置
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": false,
"emitDecoratorMetadata": true
}
}
| 选项 | 说明 |
|---|---|
experimentalDecorators: false |
使用 Stage 3 新装饰器 |
experimentalDecorators: true |
使用旧版装饰器 |
emitDecoratorMetadata: true |
发出类型元数据(需要 reflect-metadata) |
总结
TypeScript 5 的 Stage 3 装饰器是元编程的标准化里程碑。配合 reflect-metadata,装饰器成为声明式编程的利器——IoC 容器、ORM 映射、验证框架、路由注册,都可以用装饰器优雅实现。核心思想:把"做什么"声明在代码上,把"怎么做"封装在装饰器里。
本站提供浏览器本地工具,免注册即可试用 →
#TypeScript#装饰器#元编程#Reflect.metadata#依赖注入