TypeScript的高级类型系统允许开发者在类型层面做逻辑运算,条件类型和映射类型是其中最强大的两个特性。掌握这些能力后,可以从类型层面消除大量运行时检查代码,让编译器在构建阶段捕获更多错误。
条件类型基础与类型推导
条件类型的语法是T extends U ? X : Y,形式上类似三元运算符,但运算对象是类型而非值。infer关键字在条件类型中声明类型变量,从被检查的类型中提取子类型。
// 从Promise中提取结果类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<number[]>>; // number[]
type C = UnwrapPromise<string>; // string
// 从数组中提取元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type D = ElementOf<string[]>; // string
type E = ElementOf<number[]>; // number
// 从函数签名中提取返回值类型和参数类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ParamType<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;
type F = ReturnType<typeof fetch>; // Promise<Response>
type G = ParamType<(id: number, name: string) => void>; // number
映射类型与Key重映射
映射类型遍历一个联合类型,对每个成员应用变换,产生新的对象类型。TypeScript 4.1引入的Key Remapping语法([K in keyof T as NewKey])让映射类型更加灵活。
// 将所有属性变为可选
type Optional<T> = {
[K in keyof T]?: T[K];
};
// 将所有属性变为只读
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// 过滤指定类型的属性
type PickByValueType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface User {
id: number;
name: string;
email: string;
age: number;
active: boolean;
}
// 只保留string类型属性
type StringProps = PickByValueType<User, string>;
// { name: string; email: string }
// 只保留number类型属性
type NumberProps = PickByValueType<User, number>;
// { id: number; age: number }
// 将属性名转为大写
type UppercaseKeys<T> = {
[K in keyof T as Uppercase<string & K>]: T[K];
};
type UpperUser = UppercaseKeys<{ name: string; age: number }>;
// { NAME: string; AGE: number }
模板字面量类型与路径映射
模板字面量类型允许在类型层面拼接字符串,配合infer可以实现类型安全的路径解析和事件名推导。
// API路由路径类型提取
type ExtractParams<Path extends string> =
Path extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractParams<`/${Rest}`>
: Path extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: {};
type Route1 = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }
type Route2 = ExtractParams<"/api/v1/products/:productId">;
// { productId: string }
// 事件监听器类型映射
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
// 为事件名生成对应的handler类型
type Handlers<Events extends string> = {
[E in Events as EventName<E>]?: (event: E) => void;
};
type ButtonHandlers = Handlers<"click" | "focus" | "blur">;
// { onClick?: ...; onFocus?: ...; onBlur?: ... }
分布式条件类型
当条件类型的左侧是泛型类型参数且传入联合类型时,条件类型会分布执行——对联合类型的每个成员分别求值,再合并结果。
// 分布式行为:ToUnion接受联合类型时逐个处理
type ToArray<T> = T extends any ? T[] : never;
type H = ToArray<string | number>;
// string[] | number[] (不是 (string | number)[])
// 阻止分布:用方括号包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type I = ToArrayNonDist<string | number>;
// (string | number)[]
// 实际应用:排除null和undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type J = NonNullable<string | null | number | undefined>;
// string | number
实战:类型安全的API客户端
将上述类型工具组合起来,构建一个类型安全的API请求函数,让参数和返回值都由路由定义自动推导:
// API路由定义
interface ApiRoutes {
"GET /users": { params: {}; query: { page?: number }; response: User[] };
"GET /users/:id": { params: { id: string }; query: {}; response: User };
"POST /users": { params: {}; body: { name: string; email: string }; response: User };
"DELETE /users/:id": { params: { id: string }; query: {}; response: void };
}
// 从路由定义中提取路径和HTTP方法
type RouteKey = keyof ApiRoutes;
type ExtractMethod<K extends RouteKey> = K extends `${infer M} ${string}` ? M : never;
type ExtractPath<K extends RouteKey> = K extends `${string} ${infer P}` ? P : never;
// 类型安全的request函数
async function request<K extends RouteKey>(
route: K,
options: {
params?: ExtractParams<ExtractPath<K>>;
query?: ApiRoutes[K]["query"] & Record<string, any>;
body?: "body" extends keyof ApiRoutes[K] ? ApiRoutes[K]["body"] : never;
}
): Promise<ApiRoutes[K]["response"]> {
const [method, path] = route.split(" ") as [ExtractMethod<K>, ExtractPath<K>];
// 实际请求逻辑...
return {} as any;
}
// 使用:类型完全推导
const users = await request("GET /users", { query: { page: 1 } });
// users: User[]
const user = await request("GET /users/:id", { params: { id: "123" } });
// user: User
await request("POST /users", { body: { name: "test", email: "a@b.com" } });
// body类型由路由定义约束
这种模式的优势在于:路由定义是唯一的类型源,request函数的参数和返回值类型全部自动推导,任何路由变更在编译阶段就能被TypeScript捕获。不需要维护单独的类型声明文件,也不需要手动标注每个API函数的参数类型。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-lei-xing-ti-cao-shi-zhan-tiao-jian-lei-xing-tui/