0%

typescript类型编程

typescript 以其静态类型系统著称,但深入类型编程可以让你的代码更安全、可维护且具备自文档特性。本篇将探讨高级类型、条件类型、映射类型、递归类型等技巧,以及如何将它们运用到真实项目中。

基本类型复习

typescript 提供基本的 stringnumberbooleansymbol 等原始类型,以及 anyunknown。使用 any 会丧失类型检查,unknown 更安全,需显式缩小。

1
2
3
4
5
6
let a: any = 123;
let b: unknown = 'hello';
// b.trim(); // 错误,需先判断类型
if (typeof b === 'string') {
console.log(b.trim());
}

接口与类型别名

接口 interface 与类型别名 type 在大多数情况下可以互换,但也有区别:

  • interface 可以被扩展(extends)或合并声明。
  • type 更适用于联合类型、映射类型等。
1
2
interface Person { name: string; }
type ID = string | number;

泛型

泛型允许编写可重用的组件:

1
2
function identity<T>(arg: T): T { return arg; }
const s = identity<string>('foo');

泛型接口、类和约束:

1
2
3
4
5
interface Container<T> { value: T; }
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
}

通过 extends 约束泛型参数:

1
2
3
4
function loggingIdentity<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}

条件类型

条件类型是高级类型编程的核心:

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]; };

内置工具类型如 PartialRequiredRecord 都是映射类型的示例。

递归类型

1
type Json = string | number | boolean | null | Json[] | { [key: string]: Json };

递归类型非常适合描述 JSON、树结构等。

类型推断与 typeof

使用 typeof 获取值的类型:

1
2
const obj = { x: 10, y: 20 };
type Point = typeof obj;

结合 keyof

1
type Keys = keyof Point; // 'x' | 'y'

索引访问类型

1
type XType = Point['x']; // number

以及在泛型中动态引用:

1
2
3
function pluck<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(k => obj[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>}`;

结合联合类型可生成多个值选项。

条件分布类型与内置类型

ExcludeExtractNonNullable 等都是条件类型形式:

1
type NonNullable<T> = T extends null | undefined ? never : T;

类型安全的 setState

在 React 项目中,可利用映射类型确保状态更新键合法:

1
2
type State = { count: number; text: string; };
function setState<K extends keyof State>(key: K, value: State[K]) { /*...*/ }

错误处理与 never

never 类型用于不可能发生的情况,例如函数抛出异常:

1
2
3
function error(msg: string): never {
throw new Error(msg);
}

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
2
3
function merge<T, U>(a: T, b: U): T & U {
return Object.assign({}, a, b);
}

使用泛型保证返回值类型为交叉类型。

结语

类型编程是一门艺术,通过掌握条件类型、映射类型和泛型等高级特性,你可以写出更加健壮、表现力强的代码。然而不要把类型复杂度当作衡量优劣的唯一标准。良好的类型设计应当提供价值而非增加认知负担。适时地使用注释、测试和文档也是必要的。