条件类型:类型层面的if-else
TypeScript的条件类型是高级类型编程的基石。语法很简单:T extends U ? X : Y。但真正在项目中用好条件类型,需要理解它的分布式特性。
基础用法——提取Promise内部类型:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result1 = UnwrapPromise<Promise<string>>; // string
type Result2 = UnwrapPromise<string>; // string
type Result3 = UnwrapPromise<Promise<Promise<number>>>; // Promise<number>
递归版本来彻底拆解嵌套Promise:
type DeepUnwrapPromise<T> = T extends Promise<infer U>
? DeepUnwrapPromise<U>
: T;
type DeepResult = DeepUnwrapPromise<Promise<Promise<Promise<number>>>>;
// number
分布式条件类型是容易踩坑的地方。当条件类型的检查对象是裸类型参数时,TypeScript会自动将联合类型展开分别判断:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// 如果不想分布式行为,用元组包裹
type ToArrayNoDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNoDist<string | number>; // (string | number)[]
两种结果都有用武之地。分布式版本适合对联合类型的每个成员分别处理,非分布式版本适合把联合类型当作整体处理。
infer关键字:类型层面的模式匹配
infer让条件类型具备了”提取”能力。在extends子句中用infer声明一个类型变量,TypeScript会尝试匹配并推断类型。
实战场景:提取函数返回类型和参数类型:
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;
type ParamsOf<T> = T extends (...args: infer P) => any ? P : never;
function fetchUser(id: string): Promise<{ name: string; age: number }> {}
type FetchReturn = ReturnOf<typeof fetchUser>;
// Promise<{ name: string; age: number }>
type FetchParams = ParamsOf<typeof fetchUser>;
// [string]
更实用的场景——从API响应类型中提取数据字段:
interface ApiResponse<T> {
code: number;
message: string;
data: T;
timestamp: number;
}
type ExtractData<T> = T extends ApiResponse<infer D> ? D : never;
type UserData = ExtractData<ApiResponse<{ id: string; name: string }>>;
// { id: string; name: string }
infer的递归使用可以处理更复杂的嵌套结构。提取深层嵌套对象中指定key的类型:
type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? PathValue<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
}
type HostType = PathValue<Config, "database.host">;
// string
type CredUserType = PathValue<Config, "database.credentials.username">;
// string
模板字面量类型:类型安全的字符串操作
TypeScript 4.1引入的模板字面量类型,让类型系统具备了字符串拼接和解析能力。
最实用的场景——类型安全的事件系统:
type EventName<T extends string> = `on${Capitalize<T>}`;
type Handler<T> = (payload: T) => void;
interface EventMap {
click: { x: number; y: number };
change: { value: string };
submit: { formData: Record<string, string> };
}
type SafeEventName = EventName<keyof EventMap & string>;
// "onClick" | "onChange" | "onSubmit"
class TypedEventEmitter {
private handlers = new Map<string, Function[]>();
on<K extends keyof EventMap & string>(
event: EventName<K>,
handler: Handler<EventMap[K]>
): void {
const key = event;
if (!this.handlers.has(key)) {
this.handlers.set(key, []);
}
this.handlers.get(key)!.push(handler);
}
emit<K extends keyof EventMap & string>(
event: EventName<K>,
payload: EventMap[K]
): void {
this.handlers.get(event)?.forEach(fn => fn(payload));
}
}
// 使用——类型安全
const emitter = new TypedEventEmitter();
emitter.on("onClick", (payload) => {
console.log(payload.x, payload.y); // payload类型自动推断
});
// emitter.on("onFoo", ...) // 编译错误
映射类型的高级用法
映射类型配合key remapping可以实现类型层面的对象转换:
// 将所有属性变为可选,并添加_前缀
type OptionalPrefix<T> = {
[K in keyof T as `_${K & string}`]?: T[K];
};
interface User {
id: string;
name: string;
email: string;
}
type OptionalPrefixedUser = OptionalPrefix<User>;
// { _id?: string; _name?: string; _email?: string }
// 过滤出特定类型的属性
type OnlyType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface MixedConfig {
port: number;
host: string;
debug: boolean;
timeout: number;
name: string;
}
type NumberConfig = OnlyType<MixedConfig, number>;
// { port: number; timeout: number }
key remapping的as子句是TypeScript 4.1后的能力,它让映射类型既能改key也能过滤key。结合条件类型,几乎可以实现类型层面的任何数据转换。
类型体操实战:类型安全的路由系统
把前面学到的技术组合起来,实现一个类型安全的路由注册系统:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type RoutePath<Method extends HttpMethod, Path extends string> = `${Method} ${Path}`;
type ExtractParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<Rest>
: Path extends `${string}:${infer Param}`
? Param
: never;
type RouteParams<Path extends string> = {
[K in ExtractParams<Path>]: string;
};
// 测试
type UserRoute = RouteParams<"/api/users/:userId/posts/:postId">;
// { userId: string; postId: string }
function createRouter<Path extends string>(method: HttpMethod, path: Path) {
return {
method,
path,
handler: (params: RouteParams<Path>) => {},
};
}
const route = createRouter("GET", "/api/users/:userId/posts/:postId");
route.handler({ userId: "123", postId: "456" }); // 类型安全
TypeScript的高级类型不是炫技用的花架子。在大型前端项目中,类型编程能做的事情远比想象的多:类型安全的API层、类型安全的配置解析、类型安全的状态管理。投入在类型约束上的时间,会在后期维护中以数倍的效率回报。好的类型系统就是最好的文档,也是最可靠的自动测试。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-shi-zhan-cong-tiao-jian-lei-xing/