条件类型:类型系统的if-else
TypeScript的条件类型(Conditional Types)在泛型约束和类型推导中扮演着关键角色,语法形式为T extends U ? X : Y,在类型层面实现了条件分支逻辑。很多内置工具类型(Exclude、Extract、ReturnType等)都基于条件类型实现。掌握条件类型的推导规则和分发特性,是编写类型安全代码库的基础能力。
条件类型最关键的特性是distributive conditional types(分布式条件类型)。当检查类型是裸类型参数时,联合类型会自动分发:
// 分布式条件类型示例
type ToArray<T> = T extends any ? T[] : never
type Result = ToArray<string | number>
// 等价于 ToArray<string> | ToArray<number>
// 结果: string[] | number[]
// 禁用分发:用[]包裹T
type ToArrayNoDist<T> = [T] extends [any] ? T[] : never
type Result2 = ToArrayNoDist<string | number>
// 结果: (string | number)[]
分布式行为在实现联合类型过滤时非常有用,但在需要保持联合类型整体性时会造成意外结果。用方括号包裹类型参数可以禁用分发。
infer关键字与类型提取模式
infer在条件类型的extends子句中声明一个待推导的类型变量,TypeScript会根据实际类型自动推断其值:
// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type Fn = (x: number) => string
type R = MyReturnType<Fn> // string
// 提取Promise内部类型
type Unpacked<T> = T extends Promise<infer U> ? U : T
type P = Unpacked<Promise<string[]>> // string[]
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never
type E = ElementOf<readonly string[]> // string
// 多位置infer:提取函数第一个参数类型
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never
type A = FirstArg<(x: number, y: string) => void> // number
infer声明的类型变量只在条件类型的true分支中可用。在false分支中引用infer变量会报编译错误。多个infer可以在同一个extends中声明,TypeScript会分别推导。
模板字面量类型:字符串级别的类型约束
TypeScript 4.1引入的模板字面量类型(Template Literal Types)将类型系统的能力扩展到字符串层面,可以对字符串的格式、前缀、后缀进行精确约束:
// CSS属性类型安全
type CSSProperty = "margin" | "padding" | "width" | "height" | "color"
type CSSUnit = "px" | "rem" | "em" | "%"
type CSSValue = `${number}${CSSUnit}`
type CSSRule = {
[K in CSSProperty]?: CSSValue
}
const style: CSSRule = {
margin: "10px", // 合法
width: "100%", // 合法
padding: "abc" // 类型错误: 不匹配 ${number}${CSSUnit}
}
// 事件监听器类型安全
type EventName<T extends string> = `on${Capitalize<T>}`
type EventHandler<T> = (event: T) => void
type ClickEvent = { type: "click"; x: number; y: number }
type InputEvent = { type: "input"; value: string }
type DOMEvents = {
[EventName<"click">]: EventHandler<ClickEvent>
[EventName<"input">]: EventHandler<InputEvent>
}
// 结果: { onClick: (event: ClickEvent) => void, onInput: (event: InputEvent) => void }
// 路由参数类型推导
type Route = "/users/:id" | "/posts/:postId/comments/:commentId"
type ExtractParams<S extends string> =
S extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<Rest>
: S extends `${string}:${infer Param}`
? Param
: never
type Params = ExtractParams<"/users/:id"> // "id"
type PostParams = ExtractParams<"/posts/:postId/comments/:commentId">
// "postId" | "commentId"
模板字面量类型与Capitalize、Uncapitalize、Uppercase、Lowercase内置工具类型配合,可以构建高度类型安全的事件系统、路由系统和配置系统。
实战:类型安全的API客户端生成器
// 基于接口定义自动生成类型安全的请求方法
interface ApiEndpoints {
"/api/users": {
GET: { response: User[]; query: { page?: number } }
POST: { response: User; body: CreateUserDTO }
}
"/api/users/:id": {
GET: { response: User; params: { id: string } }
PUT: { response: User; body: UpdateUserDTO; params: { id: string } }
DELETE: { response: void; params: { id: string } }
}
}
type Method = "GET" | "POST" | "PUT" | "DELETE"
type ExtractPathParams<Path extends string> =
Path extends `${string}:${infer Param}`
? { [K in Param | ExtractPathParams<Param extends `${infer P}&${string}`
? never : Path>]: string }
: {}
// 类型安全的fetch封装
type SafeFetch = {
<Path extends keyof ApiEndpoints, M extends keyof ApiEndpoints[Path]>(
path: Path,
options: {
method: M
params?: ExtractPathParams<Path>
query?: ApiEndpoints[Path][M] extends { query: infer Q } ? Q : never
body?: ApiEndpoints[Path][M] extends { body: infer B } ? B : never
}
): Promise<
ApiEndpoints[Path][M] extends { response: infer R } ? R : never
>
}
// 使用示例 - 所有参数和返回值都有完整类型推导
const users = await safeFetch("/api/users", { method: "GET", query: { page: 1 } })
// users 类型自动推导为 User[]
const user = await safeFetch("/api/users/:id", { method: "GET", params: { id: "123" } })
// user 类型自动推导为 User
这种模式在大型前端项目中可以彻底消除API调用的类型错误。接口定义变更时,所有相关调用点的类型错误会在编译期暴露,而不是在运行时才发现字段名拼写错误或参数遗漏。
类型体操的性能边界
复杂条件类型在深度递归时会导致TypeScript编译器性能下降甚至栈溢出。实际项目中的经验值:递归深度超过10层的条件类型,编译时间会从毫秒级跳到秒级;超过20层可能触发TypeScript的递归深度限制。拆解方法:将深层嵌套的条件类型拆分为多层中间类型别名,每层只处理一层逻辑。对于超过10层的类型推导场景,考虑用代码生成替代纯类型体操。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-shi-zhan-tiao-jian-lei-xing-yu/