TypeScript的类型系统具备图灵完备的表达能力,条件类型和映射类型是其中最强大的高级特性。在前端工程化和组件库设计中,熟练运用类型编程能构建类型安全的API接口层、自动推导工具函数类型、实现编译期参数校验。本文通过实际案例讲解条件类型、映射类型及其组合应用。
TypeScript条件类型语法与类型推断机制
条件类型的语法形式类似于JavaScript的三元表达式,根据类型关系在编译期选择不同类型分支:
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
infer关键字在条件类型中提取类型变量,是类型推断的核心工具。从函数类型中提取返回值类型:
type ReturnType<T> = T extends (...args: never[]) => infer R ? R : never;
type Fn = () => string;
type Result = ReturnType<Fn>; // string
从Promise中提取被包裹的类型:
type Unpacked<T> = T extends Promise<infer U> ? U : T;
type Data = Unpacked<Promise<number>>; // number
infer可以出现在多个位置,提取函数参数类型:
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type Params = Parameters<(a: string, b: number) => void>; // [string, number]
映射类型与Key Remapping键名重写
映射类型基于已有类型生成新类型,通过in关键字遍历键名联合类型。内置的Partial、Required、Readonly、Pick均基于映射类型实现:
// Partial的实现原理
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Readonly的实现原理
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
interface User {
name: string;
age: number;
}
type PartialUser = MyPartial<User>;
// { name?: string; age?: number }
type ReadonlyUser = MyReadonly<User>;
// { readonly name: string; readonly age: number }
TypeScript 4.1引入Key Remapping,允许在映射过程中修改键名。结合模板字面量类型实现键名转换:
// 将所有键名转为Getter方法名
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
过滤键名生成子集类型:
// 移除指定键
type Omit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
// 仅保留函数类型属性
type FunctionProperties<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
模板字面量类型与字符串类型操作
模板字面量类型允许在类型层面拼接和拆分字符串,配合Uppercase、Lowercase、Capitalize、Uncapitalize四个内置工具实现类型级字符串操作:
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
构建类型安全的事件监听器接口:
type EventMap = {
click: { x: number; y: number };
scroll: { scrollTop: number };
input: { value: string };
};
type Listener<T extends keyof EventMap> = (event: EventMap[T]) => void;
function addListener<T extends keyof EventMap>(
event: T,
listener: Listener<T>
) {
// 实现
}
// 类型安全:参数类型自动推导
addListener("click", (e) => {
console.log(e.x, e.y); // e被正确推导为{x:number;y:number}
});
路由参数类型提取:
type ExtractParams<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer Rest}`
? Param | ExtractParams<`/${Rest}`>
: T extends `${infer _Start}:${infer Param}`
? Param
: never;
type Route = "/users/:userId/posts/:postId";
type Params = ExtractParams<Route>; // "userId" | "postId"
条件类型分布式特性与类型过滤
当条件类型的类型参数是裸类型参数且传入联合类型时,条件类型会分布式应用——对联合类型的每个成员分别求值后合并:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// (string | number)[] 还是 string[] | number[]?
// 答案是 string[] | number[](分布式特性)
利用分布式特性实现类型过滤:
type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<"a" | "b" | "c", "b">; // "a" | "c"
// 从联合类型中提取指定类型
type Extract<T, U> = T extends U ? T : never;
type Strings = Extract<string | number | boolean, string>; // string
禁用分布式特性需用方括号包裹类型参数:
type NonDistToArray<T> = [T] extends [any] ? T[] : never;
type Result = NonDistToArray<string | number>; // (string | number)[]
高级类型工具函数封装与复用模式
深层Partial实现,递归处理嵌套对象:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
interface Config {
server: {
host: string;
port: number;
};
debug: boolean;
}
type PartialConfig = DeepPartial<Config>;
// { server?: { host?: string; port?: number }; debug?: boolean }
路径类型推导,实现类型安全的对象属性访问:
type Path<T> = T extends object
? {
[K in keyof T & string]: K | `${K}.${Path<T[K]>}`;
}[keyof T & string]
: never;
type UserPaths = Path<{ profile: { name: string; avatar: string } }>;
// "profile" | "profile.name" | "profile.avatar"
这些类型工具在组件库Props类型定义、API请求响应类型推导、状态管理Store类型约束等场景中应用广泛,能大幅减少手动类型声明的工作量,同时保证类型安全。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-tiao-jian-lei-xing-yu-ying-she-lei-xing-gao-ji/