TypeScript 进阶指南:类型体操与实战技巧

foxgem

TypeScript 已成为现代前端开发的标配。本文将带你深入理解 TypeScript 的高级类型系统,掌握类型体操的核心技巧。

为什么需要高级类型?

基础的类型注解只是冰山一角。真正发挥 TypeScript 威力的是它强大的类型系统:

  1. 类型安全 - 在编译期捕获潜在错误
  2. 代码智能提示 - IDE 可以提供精确的补全
  3. 自文档化 - 类型就是最好的文档
  4. 重构安全 - 改动会立即暴露问题

泛型:类型的函数

泛型是类型编程的基础,就像函数是值编程的基础一样:

// 基础泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 泛型约束
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 现在可以安全访问 .length
  return arg;
}

// 多个类型参数
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

泛型工具类型

TypeScript 内置了许多实用的工具类型:

// Partial - 所有属性可选
type PartialUser = Partial<User>;

// Required - 所有属性必需
type RequiredUser = Required<User>;

// Pick - 选取部分属性
type UserBasic = Pick<User, 'id' | 'name'>;

// Omit - 排除部分属性
type UserWithoutPassword = Omit<User, 'password'>;

// Record - 构造对象类型
type UserRoles = Record<string, string[]>;

条件类型:类型的 if-else

条件类型让类型可以根据条件进行判断:

// 基础条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// 实用示例:提取 Promise 的内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Result = UnwrapPromise<Promise<string>>; // string

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;

infer 关键字

infer 用于在条件类型中推断类型变量:

// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

// 提取构造函数实例类型
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;

// 提取 Promise 嵌套类型
type DeepUnwrap<T> = T extends Promise<infer U> 
  ? DeepUnwrap<U> 
  : T;

type Nested = Promise<Promise<Promise<string>>>;
type Flat = DeepUnwrap<Nested>; // string

模板字面量类型

TypeScript 4.1 引入的强大特性:

// 基础模板字面量
type Greeting = `Hello, ${string}!`;

const g1: Greeting = "Hello, World!"; // ✅
const g2: Greeting = "Hi, World!";    // ❌

// 组合字面量类型
type Color = 'red' | 'green' | 'blue';
type Size = 'small' | 'medium' | 'large';
type Style = `${Size}-${Color}`;
// 'small-red' | 'small-green' | ... 共 9 种组合

// 实用工具类型
type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;

// 事件处理器类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickHandler = EventName<'click'>; // 'onClick'

实战:构建类型安全的 API 客户端

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

type Endpoint = '/users' | '/posts' | '/comments';

type APIRoute = `${HTTPMethod} ${Endpoint}`;

// 'GET /users' | 'GET /posts' | 'POST /users' | ...

interface APIConfig {
  'GET /users': { response: User[] };
  'GET /posts': { response: Post[] };
  'POST /users': { body: CreateUser; response: User };
}

function request<T extends keyof APIConfig>(
  route: T,
  config?: APIConfig[T] extends { body: infer B } ? { body: B } : never
): Promise<APIConfig[T]['response']> {
  // 实现...
}

映射类型:批量变换

映射类型可以基于旧类型创建新类型:

// 基础映射
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// 键重映射 (TypeScript 4.1+)
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface Person {
  name: string;
  age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

// 过滤属性
type FilterByType<T, U> = {
  [P in keyof T as T[P] extends U ? P : never]: T[P];
};

type StringProps = FilterByType<Person, string>;
// { name: string; }

类型守卫与类型收窄

类型守卫帮助 TypeScript 在运行时收窄类型:

// typeof 守卫
function processValue(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase(); // TypeScript 知道这是 string
  }
  return value.toFixed(2); // TypeScript 知道这是 number
}

// instanceof 守卫
class Dog { bark() {} }
class Cat { meow() {} }

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// 自定义类型守卫
interface Fish { swim(): void; }
interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

// in 操作符守卫
function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}

实战技巧

1. 严格的对象字面量检查

interface Config {
  host: string;
  port: number;
}

// 使用泛型确保精确匹配
function createConfig<T extends Config>(
  config: T & Record<Exclude<keyof T, keyof Config>, never>
): Config {
  return config;
}

createConfig({ host: 'localhost', port: 3000 }); // ✅
createConfig({ host: 'localhost', port: 3000, extra: 1 }); // ❌

2. 深度 Readonly

type DeepReadonly<T> = T extends (infer U)[]
  ? ReadonlyArray<DeepReadonly<U>>
  : T extends object
  ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
  : T;

interface NestedObject {
  a: {
    b: {
      c: string;
    };
  };
}

type ImmutableNested = DeepReadonly<NestedObject>;
// 所有层级都是 readonly

3. 类型安全的事件发射器

type EventMap = {
  connect: { host: string };
  disconnect: { reason: string };
  message: { content: string; from: string };
};

class TypedEmitter<T extends Record<string, any>> {
  on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
    // 实现
  }
  
  emit<K extends keyof T>(event: K, payload: T[K]): void {
    // 实现
  }
}

const emitter = new TypedEmitter<EventMap>();
emitter.on('connect', ({ host }) => console.log(host)); // ✅ 类型正确
emitter.emit('message', { content: 'hi', from: 'user' }); // ✅

总结

  1. 泛型 - 类型编程的基础,实现类型的参数化
  2. 条件类型 - 类型层面的逻辑判断,配合 infer 提取类型
  3. 模板字面量 - 字符串类型的强大操作能力
  4. 映射类型 - 批量变换对象类型的属性
  5. 类型守卫 - 运行时类型收窄,确保类型安全

掌握这些高级特性,你就能写出既类型安全又灵活的 TypeScript 代码。记住:类型系统是你的朋友,善用它来捕获 bug