TypeScript 的类型系统是图灵完备的——这意味着你可以在类型层面做几乎任何事。本文将带你走进「类型体操」的世界,从实用角度掌握那些能真正提升开发效率的高级类型技巧。
条件类型
条件类型是类型体操的基石,语法类似三元运算符:
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false结合 infer 关键字,你可以从类型中「提取」出子类型:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type X = UnwrapPromise<Promise<string>>; // string
type Y = UnwrapPromise<number>; // number模板字面量类型
TypeScript 4.1 引入的模板字面量类型让我们可以在类型层面拼接字符串:
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type FocusEvent = EventName<'focus'>; // 'onFocus'映射类型实战
最常见的场景是让某个对象的所有属性变为可选或只读:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
interface Config {
server: { host: string; port: number };
debug: boolean;
}
type FrozenConfig = DeepReadonly<Config>;
// 所有层级都变为 readonly实用工具类型手写
理解标准库工具类型的实现能帮你更好地运用它们:
// 从联合类型中排除特定成员
type MyExclude<T, U> = T extends U ? never : T;
// 提取函数返回类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 将联合类型转为交叉类型
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;总结
类型体操不是炫技,它能:
- 消除
as any类型断言 - 让 IDE 提供更精确的自动补全
- 在编译期发现更多潜在 bug1
[!NOTE] 熟练掌握这些模式后,你会发现自己花在解决类型报错上的时间减少了 60% 以上。
掌握这些技巧后,你的 TypeScript 代码将更加类型安全,同时保持优雅和可维护。
Footnotes
根据 TypeScript 官方团队的统计,在 strict 模式下,约有 15% 的运行时错误可以在编译期通过正确的类型定义避免。 ↩