typescript 以其静态类型系统著称,但深入类型编程可以让你的代码更安全、可维护且具备自文档特性。本篇将探讨高级类型、条件类型、映射类型、递归类型等技巧,以及如何将它们运用到真实项目中。
基本类型复习
typescript 提供基本的 string、number、boolean、symbol 等原始类型,以及 any 和 unknown。使用 any 会丧失类型检查,unknown 更安全,需显式缩小。
1 | let a: any = 123; |
接口与类型别名
接口 interface 与类型别名 type 在大多数情况下可以互换,但也有区别:
interface可以被扩展(extends)或合并声明。type更适用于联合类型、映射类型等。
1 | interface Person { name: string; } |
泛型
泛型允许编写可重用的组件:
1 | function identity<T>(arg: T): T { return arg; } |
泛型接口、类和约束:
1 | interface Container<T> { value: T; } |
通过 extends 约束泛型参数:
1 | function loggingIdentity<T extends { length: number }>(arg: T): T { |
条件类型
条件类型是高级类型编程的核心:
1 | type IsString<T> = T extends string ? true : false; |
它们支持分布式条件类型,当传入联合类型时会各自判断:
1 | type A = IsString<string | number>; // true | false |
利用条件类型和 infer 可以构建复杂的提取逻辑:
1 | type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; |
映射类型
映射类型可遍历属性并修改:
1 | type Readonly<T> = { readonly [P in keyof T]: T[P]; }; |
内置工具类型如 Partial、Required、Record 都是映射类型的示例。
递归类型
1 | type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; |
递归类型非常适合描述 JSON、树结构等。
类型推断与 typeof
使用 typeof 获取值的类型:
1 | const obj = { x: 10, y: 20 }; |
结合 keyof:
1 | type Keys = keyof Point; // 'x' | 'y' |
索引访问类型
1 | type XType = Point['x']; // number |
以及在泛型中动态引用:
1 | function pluck<T, K extends keyof T>(obj: T, keys: K[]): T[K][] { |
infer 关键字
在条件类型中推断出子类型:
1 | type ElementType<T> = T extends Array<infer E> ? E : T; |
配合递归,可以构造深度转换类型,如将所有属性设为可选:
1 | type DeepPartial<T> = { [P in keyof T]?: DeepPartial<T[P]> }; |
模板字面量类型
类似于字符串模板,可以拼接类型:
1 | type EventNames = `on${Capitalize<string>}`; |
结合联合类型可生成多个值选项。
条件分布类型与内置类型
Exclude、Extract、NonNullable 等都是条件类型形式:
1 | type NonNullable<T> = T extends null | undefined ? never : T; |
类型安全的 setState
在 React 项目中,可利用映射类型确保状态更新键合法:
1 | type State = { count: number; text: string; }; |
错误处理与 never
never 类型用于不可能发生的情况,例如函数抛出异常:
1 | function error(msg: string): never { |
在 switch 语句中处理所有情况时可用来检查穷尽性。
抽象类型工具
keyof- 属性名的联合。in- 在映射类型中遍历。as- remapping technique, e.g.,
1 | type Mapped<T> = { [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P] }; |
与 JavaScript interop
- 使用
declare语句为现有 JS 模块添加类型。 - 通过
--declaration生成.d.ts文件,用于库发布。
工具与调试
tsc --noEmit用于类型检查。- 编辑器提示与 ESLint 的
@typescript-eslint插件结合。 ts-node可执行 typescript 脚本。
性能思考
过度复杂的类型会导致编辑器速度缓慢,注意把类型计算限制在必要范围,并使用 type 而不是 interface 来避免过多的交叉引用。
实践案例
构建一个类型安全的深度合并函数:
1 | function merge<T, U>(a: T, b: U): T & U { |
使用泛型保证返回值类型为交叉类型。
结语
类型编程是一门艺术,通过掌握条件类型、映射类型和泛型等高级特性,你可以写出更加健壮、表现力强的代码。然而不要把类型复杂度当作衡量优劣的唯一标准。良好的类型设计应当提供价值而非增加认知负担。适时地使用注释、测试和文档也是必要的。