TypeScript 中的元编程:编写生成代码的代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:隐藏在类型中的魔法

\n\n

想象一下,您编写的不仅仅是代码,而是一套给编译器本身的指令集,它根据您的规则生成安全、自文档化的结构。这就是 TypeScript 中的元编程——编写在类型层面工作、在编译时分析和转换其他类型的能力。

\n\n

许多开发者在掌握基本的泛型和接口后就止步不前,甚至没有意识到在 infer、extends 这些关键字和条件结构背后隐藏着多么强大的武器库。在本文中,我们将深入探讨 TypeScript 元编程的高级技术,这些技术将把您从语言的普通用户转变为真正的架构师。

\n\n

1. 条件类型:类型层面的 if

\n\n

条件类型是任何 类型级编程 的基础。它们的工作方式类似于三元运算符,但作用于类型系统层面:

\n\n
type IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">;   // "yes"\ntype Test2 = IsString<42>;        // "no"\n
\n\n

但真正的魔法始于我们将它们与泛型结合使用时。例如,创建一个只提取对象字符串键的类型:

\n\n
type 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\n

2. Infer:从其他类型中提取类型

\n\n

infer 关键字可以说是 TypeScript 元编程 武器库中最强大的工具。它允许您“窥视”类型的内部并提取其组成部分。来看一个经典例子——获取函数返回值的类型:

\n\n
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 的能力远不止于此。让我们提取函数第一个参数的类型:

\n\n
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\n
type 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\n

3. 映射类型:对象的转换

\n\n

映射类型 允许您基于现有类型创建新类型,通过遍历其键来实现。这是创建通用工具不可或缺的工具:

\n\n
type 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\n
type PartialStringProps<T> = {\n  [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n  nam                

博客

书籍推荐