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#依赖注入