TypeScript条件类型与infer关键字深度解析
TypeScript的条件类型(Conditional Types)是类型系统中最强大的编程能力之一,语法为T extends U ? X : Y,在类型层面实现分支判断。当T可赋值给U时返回类型X,否则返回类型Y。
条件类型真正强大之处在于配合infer关键字做类型推断。infer允许在条件类型的extends子句中声明一个类型变量,由TypeScript编译器自动推断该位置应匹配的类型。
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 提取Promise内部类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
// 提取对象值的类型联合
type ValueOf<T> = T extends { [k: string]: infer V } ? V : never;
infer可在同一条件类型中多次使用,也可在分布式条件类型中使用。分布式条件类型是指当条件类型作用于联合类型时,会自动将联合类型的每个成员分别代入计算,再将结果合并为联合类型。
模板字面量类型与字符串模式匹配
TypeScript 4.1引入的模板字面量类型(Template Literal Types)为字符串类型的操作提供了强大的编程能力。配合内置字符串操作类型Uppercase、Lowercase、Capitalize、Uncapitalize,可在类型层面实现字符串变换。
// CSS属性名转CSS变量名
type CSSVarName<T extends string> = `--${Uncapitalize<T>}`;
type MarginVar = CSSVarName<"Margin">; // "--margin"
// 事件名转换:onChange -> ChangeCallback
type CallbackName<T extends string> =
T extends `on${infer Rest}` ? `${Rest}Callback` : never;
type X = CallbackName<"onChange">; // "ChangeCallback"
type Y = CallbackName<"onClick">; // "ClickCallback"
模板字面量类型的递归使用可以实现更复杂的字符串解析,如从URL路径中提取参数类型:
// 提取路由参数类型
type ExtractRouteParams<T extends string> =
T extends `${infer _Head}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
: T extends `${infer _Head}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = ExtractRouteParams<"/users/:id/posts/:postId">;
// { id: string; postId: string }
映射类型与Key Remapping高级技巧
TypeScript 4.1同时引入了键重映射(Key Remapping)语法,允许在映射类型中通过as子句变换键名,结合模板字面量类型可以批量重命名对象属性:
// 将对象所有属性变为Getter函数
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
键重映射还可以用于过滤属性,结合条件类型实现Pick/Omit的变体:
// 只保留函数类型的属性
type PickFunctions<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
// 排除undefined和null类型的属性
type NonNullableKeys<T> = {
[K in keyof T as null extends T[K] ? never : undefined extends T[K] ? never : K]: T[K];
};
递归类型与深层Readonly实现
TypeScript的类型系统支持递归类型定义,可以实现深层类型变换。DeepReadonly是典型的递归条件类型:
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
// 构建嵌套路径类型(用于安全访问深层属性)
type PathKeys<T> = T extends object
? { [K in keyof T & string]: T[K] extends object
? K | `${K}.${PathKeys<T[K]>}`
: K
}[keyof T & string]
: never;
interface Config {
database: { host: string; port: number; auth: { user: string; pass: string } };
cache: { ttl: number; prefix: string };
}
type ConfigPaths = PathKeys<Config>;
// "database" | "database.host" | "database.port" | "database.auth" | "database.auth.user" | "database.auth.pass" | "cache" | "cache.ttl" | "cache.prefix"
递归类型需注意TypeScript的递归深度限制,默认情况下类型实例化深度上限约50层,超过会报”Type produces a tuple type that is too large to represent”错误。实际使用中,3-5层的递归深度足以覆盖绝大多数业务场景。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-lei-xing-ti-cao-shi-zhan-tiao-jian-lei-xing-tui/