TypeScript高级类型推导实战:从泛型约束到条件类型分布

泛型约束的正确打开方式

TypeScript泛型的威力不仅在于参数化类型,更在于通过extends关键字约束泛型的形状。约束粒度决定了类型安全的强度。看一个渐进式约束的例子:

// 最宽松:无约束,T可以是任何类型
function identity<T>(arg: T): T {
  return arg;
}

// 中等约束:要求T有特定属性
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

// 严格约束:结合keyof和泛型,实现类型安全的属性访问
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 30, active: true };
const name = getProperty(user, "name");   // string
const age = getProperty(user, "age");     // number
const err = getProperty(user, "email");  // 编译错误:email不是user的key

条件类型与分布式条件类型

条件类型(Conditional Types)是TypeScript类型系统的图灵完备机制。基本语法T extends U ? X : Y,当T传入泛型参数且为联合类型时,会自动分布计算:

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

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

// 分布式行为:对联合类型的每个成员分别计算
type IsStringDist<T> = T extends string ? true : false;
type C = IsStringDist<string | number>;  // true | false → boolean

// 阻止分布式行为:用[]包裹T
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type D = ToArrayNonDist<string | number>;  // (string | number)[] 而非 string[] | number[]

分布式行为在多数场景下是期望的(如对每个联合成员做映射),但在某些场景下需要阻止。理解两者的区别是写好类型工具函数的前提。

infer关键字:类型推断的模式匹配

infer用于在条件类型中推断子类型,类似正则表达式的捕获组:

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

type R1 = ReturnType<() => string>;        // string
type R2 = ReturnType<(x: number) => void>; // void

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

type P1 = Awaited<Promise<string>>;           // string
type P2 = Awaited<Promise<Promise<number>>>;    // number(递归解包)

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

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

class Foo {
  constructor(public x: number, public y: string) {}
}
type FooParams = ConstructorParams<typeof Foo>; // [x: number, y: string]

模板字面量类型:字符串级别的类型操作

TypeScript 4.1引入的模板字面量类型,让类型系统可以操作字符串:

// 事件监听器的类型安全
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">;   // "onClick"
type FocusEvent = EventName<"focus">;  // "onFocus"

// CSS属性类型构建
type CSSUnit = "px" | "rem" | "em" | "%" | "vh" | "vw";
type CSSValue<T extends number | string> = T extends number
  ? `${T}${CSSUnit}`
  : T;

// API路由的类型推导
type APIRoute<Version extends "v1" | "v2", Resource extends string> =
  `/api/${Version}/${Resource}`;

type UserV1 = APIRoute<"v1", "users">;    // "/api/v1/users"
type PostV2 = APIRoute<"v2", "posts">;    // "/api/v2/posts"

实战:构建类型安全的EventEmitter

将上述技术组合起来,构建一个完全类型安全的事件系统:

interface EventMap {
  login: { userId: string; timestamp: number };
  logout: { userId: string };
  error: { code: number; message: string };
  data: { payload: unknown };
}

type EventHandler<T extends keyof EventMap> = (payload: EventMap[T]) => void;

class TypeSafeEmitter {
  private handlers = new Map<string, Set<Function>>();

  on<T extends keyof EventMap>(event: T, handler: EventHandler<T>): void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);
  }

  emit<T extends keyof EventMap>(event: T, payload: EventMap[T]): void {
    const handlers = this.handlers.get(event);
    if (handlers) {
      handlers.forEach(fn => fn(payload));
    }
  }

  off<T extends keyof EventMap>(event: T, handler: EventHandler<T>): void {
    this.handlers.get(event)?.delete(handler);
  }
}

// 使用:完全类型安全
const emitter = new TypeSafeEmitter();
emitter.on("login", (p) => {
  console.log(p.userId);     // OK: userId是string
  console.log(p.timestamp);  // OK: timestamp是number
});

emitter.emit("login", { userId: "u1", timestamp: Date.now() }); // OK
emitter.emit("login", { userId: "u1" });           // 编译错误:缺少timestamp
emitter.emit("error", { userId: "u1" });           // 编译错误:payload类型不匹配

类型推导的性能边界

复杂类型推导会增加编译时间。当项目中的类型嵌套超过10层或联合类型成员超过50个时,tsc可能明显变慢。优化方式:用type别名命名中间类型避免重复计算、减少不必要的递归深度、对大型联合类型使用预处理脚本生成类型定义文件而非让tsc每次推导。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-tui-dao-shi-zhan-cong-fan-xing/

(0)
小编小编
上一篇 41分钟前
下一篇 41分钟前

相关推荐