TypeScript高级类型编程:泛型、条件类型与类型推导

TypeScript 类型系统为何强大

TypeScript 之所以能在 2026 年依旧是前端与 Node.js 生态的绝对主流,关键在于它的类型系统是“图灵完备级别”的表达能力。它不仅能描述数据的形状,还能在类型层面进行逻辑运算:条件判断、递归、映射、推导。掌握高级类型编程,意味着你能让编译器帮你拦下大量运行时 bug。

本文从泛型出发,一步步走向条件类型、infer 推导、映射类型与模板字面量类型,最后给出一个实战工具类型库。

泛型:类型层面的参数化

泛型让函数、接口、类可以“延迟指定类型”。最经典的例子是 identity 函数:

typescript
function identity<T>(value: T): T {
  return value;
}

const n = identity(42);       // 推导为 number
const s = identity("hello");  // 推导为 string

泛型真正的威力在于约束。用 extends 限制类型参数的范围:

typescript
// 要求 T 必须包含 length 属性
function logLength<T extends { length: number }>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("abc");     // OK
logLength([1, 2, 3]); // OK
// logLength(123);    // Error: number 没有 length

泛型还可以有默认值和多个参数,常用于工厂函数与 API 封装:

typescript
interface ApiResponse<T = unknown, E = string> {
  data: T;
  error: E | null;
  code: number;
}

function request<T>(url: string): Promise<ApiResponse<T>> {
  /* ... */
  return null as any;
}

keyof 与映射类型

keyof 取出一个对象类型的所有键组成的联合类型,是类型编程的基石。

typescript
interface User {
  id: number;
  name: string;
  email: string;
}

type UserKeys = keyof User; // "id" | "name" | "email"

配合映射类型,可以批量转换属性。TypeScript 内置了 PartialReadonlyPickOmit,理解它们的实现有助于自定义:

typescript
// 手写 Partial
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// 把所有属性变成可空(null | undefined)
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// 把所有属性变成只读 getter
type ReadonlyAll<T> = {
  readonly [K in keyof T]: T[K];
};

条件类型与 infer

条件类型形如 T extends U ? X : Y,类似三元表达式,但作用在类型层面。它让类型具备“判断”能力。

typescript
type IsString<T> = T extends string ? true : false;

type A = IsString<"hi">;   // true
type B = IsString<42>;     // false

infer 关键字是条件类型的灵魂,它能在条件分支中“捕获”某个类型变量。最经典的例子是提取函数返回类型与 Promise 内部类型:

typescript
// 提取函数返回值类型(等价于内置 ReturnType)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 提取 Promise 的内部类型
type Unpacked<T> = T extends Promise<infer U> ? U : T;

type R1 = MyReturnType<() => number>;        // number
type R2 = Unpacked<Promise<string>>;         // string
type R3 = Unpacked<Promise<Promise<number>>>; // Promise<number> (非递归)

递归条件类型

条件类型可以递归,从而处理嵌套结构。下面实现一个深度 Unpacked 与扁平化元组:

typescript
// 深度解包 Promise
type DeepUnpacked<T> = T extends Promise<infer U> ? DeepUnpacked<U> : T;

type D = DeepUnpacked<Promise<Promise<Promise<number>>>>; // number

// 元组扁平化
type Flatten<T extends readonly any[]> = T extends [infer Head, ...infer Tail]
  ? Head extends readonly any[]
    ? [...Flatten<Head>, ...Flatten<Tail>]
    : [Head, ...Flatten<Tail>]
  : [];

type F = Flatten<[1, [2, 3], [[4]]]>; // [1, 2, 3, 4]

注意递归类型在 TypeScript 中有深度限制(约 50 层),过深的递归会导致“Type instantiation is excessively deep”报错。

模板字面量类型

模板字面量类型让你在类型层面拼接字符串,特别适合生成 getter/setter、事件名等。

typescript
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

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

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

一个实战场景:把对象的键转换成点分路径,常用于表单校验错误路径。

typescript
type Join<K, P> = K extends string | number
  ? P extends string | number
    ? `${K}${"" extends P ? "" : "."}${P}`
    : never
  : never;

type Paths<T, P extends string = ""> = T extends object
  ? {
      [K in keyof T]: K extends string
        ? Join<"" extends P ? K : `${P}.${K}`, "" >
        : never;
    }[keyof T]
  : never;

实战:类型安全的事件系统

结合上述技巧,构建一个类型安全的事件总线:

typescript
type EventMap = {
  login: { userId: string; time: number };
  logout: { userId: string };
  purchase: { orderId: string; amount: number };
};

class TypedEmitter<T extends Record<string, any>> {
  private handlers: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};

  on<K extends keyof T>(event: K, cb: (payload: T[K]) => void) {
    (this.handlers[event] ??= []).push(cb);
  }

  emit<K extends keyof T>(event: K, payload: T[K]) {
    this.handlers[event]?.forEach((cb) => cb(payload));
  }
}

const bus = new TypedEmitter<EventMap>();
bus.on("login", (p) => {
  // p 自动推导为 { userId: string; time: number }
  console.log(p.userId, p.time);
});
bus.emit("purchase", { orderId: "o1", amount: 99 }); // OK
// bus.emit("purchase", { orderId: "o1" });           // Error: 缺少 amount

类型编程的取舍

高级类型虽强大,但也要克制。过度复杂的类型体操会让代码可读性骤降、编译变慢。几个建议:

  • 优先用内置工具类型,能解决就别自己造。

  • 为复杂类型配注释和示例,写明输入输出。

  • 避免把运行时逻辑塞进类型,类型只负责描述,不负责计算值。

  • 关注编译性能,递归与分布式条件类型是性能重灾区,必要时拆分。
  • 小结

    TypeScript 高级类型编程的核心工具是:泛型(参数化)、keyof + 映射类型(批量变换)、条件类型 + infer(判断与捕获)、模板字面量(字符串拼接)以及递归(处理嵌套)。把它们组合起来,你就能构建出像 TypedEmitter 这样完全类型安全的基础设施。类型不是负担,而是契约——写得好,它就是不会撒谎的文档和测试。

    💬 评论区 (0)

    暂无评论,快来抢沙发吧!