TypeScript 5.x装饰器与元数据反射在业务开发中的应用实战

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/

(0)
小编小编
上一篇 13小时前
下一篇 13小时前

相关推荐