TypeScript 中的 Metadata(元数据)详解

Metadata(元数据)是 TypeScript 中一个强大的特性,它允许你在运行时访问和操作与类、方法、属性等相关的附加信息。下面我将详细介绍 TypeScript 中的 Metadata。

什么是 Metadata

Metadata 是"关于数据的数据",在 TypeScript 中,它指的是附加到类、方法、属性或参数上的额外信息,这些信息可以在运行时被读取和使用。

启用 Metadata

要使用 Metadata,需要:

  1. 安装 reflect-metadata 库:

bash

复制代码
npm install reflect-metadata
  1. tsconfig.json 中启用相关配置:

json

json 复制代码
{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}
  1. 在应用入口处导入 reflect-metadata

typescript

arduino 复制代码
import "reflect-metadata";

基本用法

1. 定义 Metadata

typescript

less 复制代码
// 定义类级别的元数据
@Reflect.metadata('classMeta', 'class metadata')
class MyClass {
  // 定义属性级别的元数据
  @Reflect.metadata('propMeta', 'property metadata')
  myProp: string;

  // 定义方法级别的元数据
  @Reflect.metadata('methodMeta', 'method metadata')
  myMethod() {}

  // 定义参数级别的元数据
  myMethod2(@Reflect.metadata('paramMeta', 'parameter metadata') param: string) {}
}

2. 获取 Metadata

typescript

javascript 复制代码
// 获取类元数据
const classMeta = Reflect.getMetadata('classMeta', MyClass);

// 获取属性元数据
const propMeta = Reflect.getMetadata('propMeta', MyClass.prototype, 'myProp');

// 获取方法元数据
const methodMeta = Reflect.getMetadata('methodMeta', MyClass.prototype, 'myMethod');

// 获取参数元数据
const paramMeta = Reflect.getMetadata('paramMeta', MyClass.prototype, 'myMethod2');

内置 Metadata

当启用 emitDecoratorMetadata 时,TypeScript 会自动为装饰器添加一些内置元数据:

  • design:type - 属性的类型
  • design:paramtypes - 方法参数的类型
  • design:returntype - 方法的返回类型

typescript

typescript 复制代码
class Example {
  @LogProperty
  public name: string;

  @LogMethod
  public greet(@LogParameter prefix: string): string {
    return `${prefix} ${this.name}`;
  }
}

function LogProperty(target: any, propertyKey: string) {
  const type = Reflect.getMetadata('design:type', target, propertyKey);
  console.log(`${propertyKey} type: ${type.name}`); // 输出: name type: String
}

function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const returnType = Reflect.getMetadata('design:returntype', target, propertyKey);
  const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
  
  console.log(`${propertyKey} return type: ${returnType.name}`); // 输出: greet return type: String
  console.log(`${propertyKey} param types: ${paramTypes.map(p => p.name).join(', ')}`); // 输出: greet param types: String
}

function LogParameter(target: any, propertyKey: string, parameterIndex: number) {
  const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
  console.log(`Parameter ${parameterIndex} of ${propertyKey} has type ${paramTypes[parameterIndex].name}`);
  // 输出: Parameter 0 of greet has type String
}

实际应用场景

1. 依赖注入

typescript

typescript 复制代码
class Service {
  doSomething() {
    console.log('Service doing something');
  }
}

class Client {
  constructor(private service: Service) {}

  execute() {
    this.service.doSomething();
  }
}

// 依赖注入容器
class Container {
  static instances = new Map<Function, any>();

  static resolve<T>(target: { new (...args: any[]): T }): T {
    // 获取目标类的参数类型
    const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
    
    // 递归解析依赖
    const dependencies = paramTypes.map((dep: Function) => {
      if (!this.instances.has(dep)) {
        this.instances.set(dep, this.resolve(dep as any));
      }
      return this.instances.get(dep);
    });
    
    // 创建实例
    return new target(...dependencies);
  }
}

const client = Container.resolve(Client);
client.execute(); // 输出: Service doing something

2. 验证装饰器

typescript

typescript 复制代码
function Validate(min: number, max: number) {
  return function(target: any, propertyKey: string) {
    Reflect.defineMetadata('validation', { min, max }, target, propertyKey);
  }
}

function validateInstance(obj: any) {
  for (const key of Object.keys(obj)) {
    const meta = Reflect.getMetadata('validation', obj, key);
    if (meta) {
      const value = obj[key];
      if (value < meta.min || value > meta.max) {
        throw new Error(`Property ${key} must be between ${meta.min} and ${meta.max}`);
      }
    }
  }
}

class User {
  @Validate(1, 100)
  age: number;

  constructor(age: number) {
    this.age = age;
    validateInstance(this);
  }
}

new User(50); // OK
new User(150); // 抛出错误

Metadata API 详解

主要方法

  1. Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?)

    • 定义元数据
  2. Reflect.getMetadata(metadataKey, target, propertyKey?)

    • 获取元数据
  3. Reflect.hasMetadata(metadataKey, target, propertyKey?)

    • 检查是否存在元数据
  4. Reflect.getMetadataKeys(target, propertyKey?)

    • 获取所有元数据键
  5. Reflect.deleteMetadata(metadataKey, target, propertyKey?)

    • 删除元数据

装饰器与 Metadata

装饰器是使用 Metadata 的主要方式:

typescript

typescript 复制代码
// 类装饰器
function ClassDecorator(constructor: Function) {
  Reflect.defineMetadata('classMeta', 'value', constructor);
}

// 属性装饰器
function PropertyDecorator(target: any, propertyKey: string) {
  Reflect.defineMetadata('propMeta', 'value', target, propertyKey);
}

// 方法装饰器
function MethodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  Reflect.defineMetadata('methodMeta', 'value', target, propertyKey);
}

// 参数装饰器
function ParameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
  Reflect.defineMetadata('paramMeta', 'value', target, propertyKey);
}

注意事项

  1. 性能考虑:大量使用 Metadata 可能会影响性能,特别是在频繁实例化的类中。
  2. 浏览器支持 :需要 reflect-metadata polyfill,某些旧浏览器可能需要额外的 polyfill。
  3. 类型擦除:TypeScript 的类型信息在编译后会消失,只有使用装饰器时才会保留部分类型信息。
  4. 设计决策:Metadata 主要用于框架开发,普通应用开发中应谨慎使用。

Metadata 是 TypeScript 中非常强大的特性,特别适合用于开发框架和库,如 Angular、NestJS 等都广泛使用了 Metadata 来实现依赖注入、验证等功能。

相关推荐
涵信6 小时前
第一节 基础核心概念-TypeScript与JavaScript的核心区别
前端·javascript·typescript
涵信16 小时前
第九节 高频代码题-实现Sleep函数(异步控制)
前端·javascript·typescript
涵信17 小时前
第八节 工程化与高级特性-模块与命名空间的选择
前端·javascript·typescript
慢知行17 小时前
VS Code 插件开发必备:轻量级日志工具的设计与实现
前端·typescript·visual studio code
穗余19 小时前
WEB3全栈开发——面试专业技能点P3JavaScript / TypeScript
前端·javascript·typescript
芭拉拉小魔仙20 小时前
【Vue3/Typescript】从零开始搭建H5移动端项目
前端·vue.js·typescript·vant
袋鱼不重1 天前
TypeScript 中的数据类型有哪些?
前端·typescript
Raink老师2 天前
6. TypeScript 函数
前端·javascript·typescript
A-wliang2 天前
从零开始搭建现代化 Monorepo 开发模板:TypeScript + Rollup + Jest + 持续集成完整指南
ubuntu·ci/cd·typescript