引言:隐藏在类型中的魔法
\n\n想象一下,您编写的不仅仅是代码,而是一套给编译器本身的指令集,它根据您的规则生成安全、自文档化的结构。这就是 TypeScript 中的元编程——编写在类型层面工作、在编译时分析和转换其他类型的能力。
\n\n许多开发者在掌握基本的泛型和接口后就止步不前,甚至没有意识到在 infer、extends 这些关键字和条件结构背后隐藏着多么强大的武器库。在本文中,我们将深入探讨 TypeScript 元编程的高级技术,这些技术将把您从语言的普通用户转变为真正的架构师。
1. 条件类型:类型层面的 if
\n\n条件类型是任何 类型级编程 的基础。它们的工作方式类似于三元运算符,但作用于类型系统层面:
\n\ntype IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">; // "yes"\ntype Test2 = IsString<42>; // "no"\n\n\n但真正的魔法始于我们将它们与泛型结合使用时。例如,创建一个只提取对象字符串键的类型:
\n\ntype StringKeys<T> = {\n [K in keyof T]: T[K] extends string ? K : never\n}[keyof T];\n\ninterface User {\n name: string;\n age: number;\n email: string;\n}\n\ntype UserStringKeys = StringKeys<User>; // "name" | "email"\n\n\n这个例子展示了 TypeScript 高级类型 如何在编译阶段过滤和转换数据结构。您实际上是在编写在代码进入运行时之前就执行的逻辑。
\n\n2. Infer:从其他类型中提取类型
\n\ninfer 关键字可以说是 TypeScript 元编程 武器库中最强大的工具。它允许您“窥视”类型的内部并提取其组成部分。来看一个经典例子——获取函数返回值的类型:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;\n\ntype Fn = (x: number) => string;\ntype FnReturn = ReturnType<Fn>; // string\n\n\n但 infer 的能力远不止于此。让我们提取函数第一个参数的类型:
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;\n\ntype Handler = (id: number, name: string) => void;\ntype HandlerFirstArg = FirstArg<Handler>; // number\n\n\n那么递归解析 Promise 呢?创建一个“展开”所有嵌套 Promise 的类型:
\n\ntype UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;\n\ntype DeepPromise = Promise<Promise<Promise<number>>>;\ntype Unwrapped = UnwrapPromise<DeepPromise>; // number\n\n\n请注意这里的递归——这是最优雅的 TypeScript 高级技术 之一,它允许处理无限嵌套的结构。
\n\n3. 映射类型:对象的转换
\n\n映射类型 允许您基于现有类型创建新类型,通过遍历其键来实现。这是创建通用工具不可或缺的工具:
\n\ntype Readonly<T> = {\n readonly [K in keyof T]: T[K];\n};\n\ntype Partial<T> = {\n [K in keyof T]?: T[K];\n};\n\n\n但我们还可以更进一步。创建一个类型,使所有属性变为可选,但仅限于字符串类型的属性:
\n\ntype PartialStringProps<T> = {\n [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n nam