VibeThink研发随想
所有文章

TypeScript 类型体操:从入门到放弃(再到真香)

深入理解 TypeScript 的类型系统,条件类型、映射类型、模板字面量类型——掌握这些,写出更优雅的类型定义。

3 min read
#TypeScript#编程

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