TypeScript高级类型编程:从泛型到模板字面量类型

TypeScript的类型系统是图灵完备的。掌握高级类型编程,可以让你编写更安全、更优雅的代码。

条件类型

条件类型允许你根据类型关系进行分支判断:

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

type A = IsString<'hello'>;  // true
type B = IsString<42>;       // false

// 实用模式:提取Promise返回值
type Unwrap<T> = T extends Promise<infer U> ? U : T;

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

infer关键字

infer 在条件类型中推断类型变量,是高级类型编程的核心工具:

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

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

// 提取Promise链中的最深类型
type DeepUnwrap<T> = T extends Promise<infer U>
  ? DeepUnwrap<U>
  : T;

模板字面量类型

TypeScript 4.1引入的模板字面量类型开启了字符串类型编程的大门:

typescript
// 生成事件名类型
type EventName<T extends string> = `on${Capitalize<T>}`;

type ClickEvent = EventName<'click'>; // 'onClick'
type FocusEvent = EventName<'focus'>; // 'onFocus'

// CSS单位类型约束
type CSSValue = `${number}${'px' | 'rem' | 'em' | '%'}`;

const width: CSSValue = '100px';  // OK
// const bad: CSSValue = '100';   // Error!

映射类型

typescript
// 将所有属性变为只读
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

// Pick和Omit的类型级实现
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

品牌类型(Branded Types)

防止原始类型的意外混用:

typescript
declare const brand: unique symbol;

type Brand<T, B> = T & { [brand]: B };

type Email = Brand<string, 'Email'>;
type UserId = Brand<string, 'UserId'>;

function createEmail(email: string): Email {
  if (!email.includes('@')) throw new Error('Invalid email');
  return email as Email;
}

function sendEmail(email: Email, userId: UserId) {}

const email = createEmail('test@example.com');
// sendEmail('test@example.com', '123'); // Type error!

分发条件类型

联合类型在条件类型中自动分发:

typescript
type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// string[] | number[] — 每个成员分别应用

// 阻止分发
type ToArrayOne<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayOne<string | number>;
// (string | number)[]

实战案例:类型安全的路由构建器

typescript
type Route = {
  home: { params: {} };
  user: { params: { id: string } };
  post: { params: { slug: string; tab?: 'comments' | 'likes' } };
};

type ExtractParams<T> = T extends string
  ? T extends `:${infer P}`
    ? P
    : never
  : never;

function navigate<T extends keyof Route>(
  route: T,
  params: Route[T]['params']
) { /* ... */ }

navigate('user', { id: '123' });
navigate('post', { slug: 'hello', tab: 'comments' });

总结

TypeScript的类型系统远不止 string 和 number。掌握高级类型编程,可以让类型成为你的文档、测试和合约,在编译时捕获更多错误。