TypeScript 的元编程是指在编译时对类型系统进行操作和扩展的编程技术。它允许开发者创建动态类型、自动生成类型定义,以及基于现有类型构建新类型。
TypeScript 的类型系统是其元编程的核心,包括:
string
、number
、boolean
、null
、undefined
等。string[]
)、元组([string, number]
)。泛型是元编程的基础工具,允许类型参数化:
// 泛型函数
function identity(arg: T): T {
return arg;
}
// 泛型类
class Box {
constructor(public value: T) {}
}
// 使用泛型
const num: number = identity(123);
const box = new Box("hello");
TypeScript 内置了多种类型工具用于元编程:
Partial
将类型 T
的所有属性变为可选:
interface User {
name: string;
age: number;
}
type PartialUser = Partial;
// 等价于:
// {
// name?: string;
// age?: number;
// }
Required
与 Partial
相反,将所有属性变为必需:
type RequiredUser = Required;
Readonly
创建只读类型:
type ReadonlyUser = Readonly;
Pick
从类型 T
中选取部分属性:
type NameOnly = Pick;
Omit
从类型 T
中排除部分属性:
type AgeOnly = Omit;
Exclude
从类型 T
中排除可分配给 U
的类型:
type NonNumber = Exclude; // string
Extract
提取 T
中可分配给 U
的类型:
type OnlyNumber = Extract; // number
基于现有类型创建新类型:
type Readonly = {
readonly [P in keyof T]: T[P];
};
type Optional = {
[P in keyof T]?: T[P];
};
根据条件选择类型:
type IsString = T extends string ? true : false;
type A = IsString; // true
type B = IsString; // false
基于字符串模板生成类型:
type HelloWorld = `Hello ${string}`;
type Greeting = HelloWorld; // "Hello ..."
在类、方法、属性等上添加元数据:
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Result: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number) {
return a + b;
}
}
通过装饰器添加和读取元数据:
import "reflect-metadata";
@Reflect.metadata("design:type", String)
class Person {
@Reflect.metadata("format", "uppercase")
name: string;
}
const format = Reflect.getMetadata("format", Person.prototype, "name");
console.log(format); // "uppercase"
创建嵌套结构的类型:
type TreeNode = {
value: T;
children?: TreeNode[];
};
const tree: TreeNode = {
value: 1,
children: [
{ value: 2 },
{ value: 3, children: [{ value: 4 }] }
]
};
在运行时检查类型:
function isString(value: any): value is string {
return typeof value === "string";
}
function print(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase()); // 类型缩小为 string
} else {
console.log(value.toFixed(2)); // 类型缩小为 number
}
}
tsconfig.json
中启用 experimentalDecorators
。TypeScript(TS)和 JavaScript(JS)的元编程虽然都涉及运行时代码操作,但实现方式和应用场景有显著差异。
Object.keys
、Object.getOwnPropertyDescriptor
、Reflect
等内置方法。Object.setPrototypeOf
、__proto__
。// JS 元编程示例:使用 Proxy 拦截对象属性访问
const person = { name: "Alice", age: 30 };
const proxy = new Proxy(person, {
get(target, prop) {
console.log(`Getting property "${prop}"`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting property "${prop}" to "${value}"`);
target[prop] = value;
return true;
}
});
proxy.name = "Bob"; // 输出: Setting property "name" to "Bob"
console.log(proxy.age); // 输出: Getting property "age" → 30
experimentalDecorators
选项支持(需配合运行时反射)。// TS 元编程示例:使用条件类型和映射类型
type IsString = T extends string ? true : false;
type ToString = {
[K in keyof T]: T[K] extends string ? T[K] : string;
};
interface User {
name: string;
age: number;
}
type StringifiedUser = ToString;
// 等价于:
// {
// name: string;
// age: string;
// }
特性 | JavaScript | TypeScript |
---|---|---|
操作对象 | 运行时对象、函数、原型等 | 编译时类型(类型系统) |
反射能力 | 完全动态,可修改对象结构和行为 | 类型信息在编译后丢失,需借助额外机制(如装饰器) |
类型安全 | 无类型检查,运行时可能出错 | 编译时强制类型安全 |
元数据存储 | 通过 Symbol 、闭包或第三方库(如 reflect-metadata ) |
通过装饰器和 reflect-metadata 存储元数据 |
// JS 装饰器示例:记录方法调用
function log(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with args: ${JSON.stringify(args)}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
experimentalDecorators
:在 tsconfig.json
中配置。// TS 装饰器示例:添加类型元数据
import "reflect-metadata";
@Reflect.metadata("classType", "entity")
class User {
@Reflect.metadata("type", "string")
name: string;
}
// 获取元数据
const classType = Reflect.getMetadata("classType", User);
const propertyType = Reflect.getMetadata("type", User.prototype, "name");
维度 | JavaScript | TypeScript |
---|---|---|
灵活性 | 高(完全动态) | 中等(受限于类型系统) |
学习成本 | 较低(仅需理解运行时概念) | 较高(需掌握类型系统和编译时概念) |
性能 | 可能影响运行时性能(如 Proxy 开销) | 编译后无额外性能开销(类型被移除) |
调试难度 | 运行时错误更难追踪 | 编译时错误更易定位 |
类型安全 | 无 | 强类型保证 |
元编程是 TypeScript 强大的特性之一,它允许开发者在编译时进行类型操作,提高代码的安全性和可维护性。通过合理使用泛型、条件类型、映射类型和装饰器等工具,可以创建出既灵活又类型安全的代码。
两者并非互斥,而是互补。例如,TS 的装饰器需结合 JS 的反射 API 才能实现完整的元编程能力。在实际开发中,可根据需求选择合适的技术组合。