TypeScript 高级模式:让类型系统为你工作

基础 TS 会了但类型总是 any?条件类型、映射类型、模板字面量类型、infer 关键字才是 TS 的真正威力。

$1.4k 字/约 8 min👁— views

TypeScript 高级模式:让类型系统为你工作

TypeScript 不是"带类型的 JavaScript",类型系统本身是一门图灵完备的编程语言。掌握高级类型,能让很多运行时错误在编译时被捕获。

类型 vs 接口

// interface:可以声明合并
interface User { name: string; }
interface User { age: number; }  // 自动合并,不报错
// User 现在是 { name: string; age: number }

// type:不能声明合并,但更灵活
type StringOrNumber = string | number;
type Callback = (x: number) => void;
type Tuple = [string, number, boolean];

选择原则:

  • 对象形状的公共 API(库):用 interface(允许用户扩展)
  • 联合类型、函数类型、复杂类型操作:用 type

联合类型 + 交叉类型

// 联合类型(OR)
type ID = string | number;

// 交叉类型(AND):合并多个类型
type Admin = User & { permissions: string[] };

// Discriminated Union(判别联合):最有用的模式
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rect'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rect':
      return shape.width * shape.height;
    case 'triangle':
      return 0.5 * shape.base * shape.height;
  }
}

类型守卫

// is 守卫
function isString(x: unknown): x is string {
  return typeof x === 'string';
}

// in 守卫
interface Cat { meow(): void }
interface Dog { bark(): void }
function makeSound(animal: Cat | Dog) {
  if ('meow' in animal) {
    animal.meow();  // TypeScript 知道是 Cat
  } else {
    animal.bark();
  }
}

// asserts 守卫
function assertNonNull<T>(val: T | null | undefined): asserts val is T {
  if (val == null) throw new Error('Expected non-null value');
}

const user: User | null = getUser();
assertNonNull(user);
user.name;  // 确认非 null

条件类型

// 基本语法
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>;  // 'yes'
type B = IsString<number>;  // 'no'

// Distributive 特性(分配律)
// 当 T 是联合类型时,条件类型分别应用于每个成员
type ToArray<T> = T extends unknown ? T[] : never;
type C = ToArray<string | number>;  // string[] | number[]

// 非分配版本(用元组包裹)
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
type D = ToArrayNonDist<string | number>;  // (string | number)[]

infer:从类型中提取子类型

infer 是条件类型中声明临时类型变量的关键字。

// ReturnType 的实现
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = () => { name: string; age: number };
type Result = ReturnType<Fn>;  // { name: string; age: number }

// Parameters 的实现
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type Params = Parameters<(x: number, y: string) => void>;  // [number, string]

// Awaited 的实现
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type Unwrapped = Awaited<Promise<Promise<string>>>;  // string

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

// 提取构造函数参数
type ConstructorParams<T> = T extends new (...args: infer P) => any ? P : never;

映射类型

// 基本语法
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };  // -? 去除可选

// Pick 的实现
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type User = { name: string; age: number; email: string };
type UserPreview = Pick<User, 'name' | 'email'>;  // { name: string; email: string }

// Omit 的实现
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// 值转换
type Nullable<T> = { [K in keyof T]: T[K] | null };

// 键重映射(as 子句,TS 4.1+)
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; getEmail: () => string }

模板字面量类型

// 基本
type Greeting = `Hello, ${string}!`;

// 联合类型展开
type Direction = 'top' | 'bottom' | 'left' | 'right';
type CSSMargin = `margin-${Direction}`;
// "margin-top" | "margin-bottom" | "margin-left" | "margin-right"

// 事件名生成
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>;  // 'onClick'

// 路由参数提取(递归条件类型)
type ExtractRouteParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractRouteParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
    ? Param
    : never;

type Params = ExtractRouteParams<'/users/:id/posts/:postId'>;
// 'id' | 'postId'

泛型约束和默认值

// 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
getProperty({ name: 'Alice', age: 30 }, 'name');  // 返回 string
// getProperty({ name: 'Alice' }, 'foo');          // 编译错误

// 默认值
interface ApiResponse<T = unknown> {
  data: T;
  status: number;
  message: string;
}
type DefaultResponse = ApiResponse;       // T = unknown
type UserResponse = ApiResponse<User>;    // T = User

const assertion

// 没有 as const:推断为宽泛类型
const config = { host: 'localhost', port: 3000, env: 'development' };
// config.env 的类型是 string

// 有 as const:字面量类型,readonly
const config = { host: 'localhost', port: 3000, env: 'development' } as const;
// config.env 的类型是 'development'(字面量类型)

// 数组 as const 变成只读元组
const routes = ['/home', '/about', '/contact'] as const;
type Route = typeof routes[number];  // '/home' | '/about' | '/contact'

satisfies 操作符(TS 4.9+)

type Theme = {
  colors: Record<string, string | [number, number, number]>;
};

// satisfies:验证类型但保留具体推断
const theme = {
  colors: {
    primary: '#3B82F6',
    secondary: [59, 130, 246],
  }
} satisfies Theme;

// 推断保留了具体类型
theme.colors.primary.toUpperCase();  // 知道是 string
theme.colors.secondary[0];           // 知道是 number

实用工具类型自己实现

// DeepPartial:深度可选
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

// Flatten:展平嵌套数组类型
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type FlatType = Flatten<number[][][]>;  // number

// UnionToIntersection:联合转交叉
type UnionToIntersection<U> =
  (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
type Result = UnionToIntersection<{ a: string } | { b: number }>;
// { a: string } & { b: number }

// Mutable:去除 readonly
type Mutable<T> = { -readonly [K in keyof T]: T[K] };

类型安全的事件系统

综合运用以上知识的实战例子:

// 定义事件映射
interface EventMap {
  click: { x: number; y: number };
  keydown: { key: string; code: string };
  resize: { width: number; height: number };
  message: { data: unknown; origin: string };
}

type EventName = keyof EventMap;

class TypedEventEmitter {
  private handlers: {
    [K in EventName]?: Array<(event: EventMap[K]) => void>;
  } = {};

  on<K extends EventName>(
    event: K,
    handler: (event: EventMap[K]) => void
  ): void {
    if (!this.handlers[event]) {
      (this.handlers[event] as any) = [];
    }
    (this.handlers[event] as any[]).push(handler);
  }

  emit<K extends EventName>(event: K, data: EventMap[K]): void {
    this.handlers[event]?.forEach(handler => handler(data));
  }
}

// 使用
const emitter = new TypedEventEmitter();

emitter.on('click', (e) => {
  console.log(e.x, e.y);  // TypeScript 知道 e 是 { x: number; y: number }
});

emitter.emit('click', { x: 100, y: 200 });  // 类型安全
// emitter.emit('click', { key: 'Enter' });  // 编译错误

总结

TypeScript 类型系统的威力在于:

  1. 条件类型:根据输入类型做分支决策
  2. infer:从复杂类型中提取子类型
  3. 映射类型:批量转换对象类型
  4. 模板字面量:基于字符串生成类型

真正的目标不是"通过编译",而是让类型系统在编码阶段就告诉你哪里会出错。类型体操只是手段,类型安全才是目的。