TypeScript 的类型系统远比大多数人想象的强大。今天我们来探索几个让人又爱又恨的高级类型特性。
条件类型
条件类型的语法类似三元表达式:
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<number>; // false
结合 infer 关键字,可以从类型中"提取"信息:
type UnpackArray<T> = T extends (infer U)[] ? U : T;
type C = UnpackArray<string[]>; // string
type D = UnpackArray<number>; // number
映射类型
映射类型允许你遍历并转换对象类型的每个属性:
// 让所有属性变为可选
type Partial<T> = {
[K in keyof T]?: T[K];
};
// 让所有属性变为只读
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// 过滤出值为特定类型的属性
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
interface User {
name: string;
age: number;
email: string;
isAdmin: boolean;
}
type StringFields = PickByValue<User, string>;
// { name: string; email: string; }
模板字面量类型
TypeScript 4.1 引入的模板字面量类型让字符串操作变得可能:
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
// 实际应用:生成 getter 方法名
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; ... }
递归类型
TypeScript 也支持递归类型定义,这在处理树形结构时非常有用:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// 深度只读
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
实用工具类型组合
真正的魔法发生在组合使用这些特性时:
// 提取函数参数类型
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 提取 Promise 的值类型
type Awaited<T> =
T extends Promise<infer U>
? U extends Promise<any>
? Awaited<U>
: U
: T;
// 这些都已经内置在 TypeScript 标准库中了!
结语
类型体操的目的不是炫技,而是让类型系统帮助你在编译时发现错误。学习这些特性的最好方式是在真实项目中遇到问题,然后思考如何用类型系统来约束它。
推荐练习网站:type-challenges