Skip to content
第 14 章 前端 ⏱ 14 分钟阅读

第 14 章:高级类型 ​

学习目标 ​

  • 学会条件类型 T extends U ? X : Y
  • 掌握映射类型与 key 重映射
  • 理解 infer 关键字与模板字面量类型
  • 实战几个常用高级类型

一、条件类型(Conditional Types) ​

语法:T extends U ? X : Y

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

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

// 实用:Nullable
type Nullable<T> = T extends null | undefined ? never : T;
type T = Nullable<string | null>;  // string

二、分布式条件类型(Distributive) ​

联合类型会被拆分逐个判断:

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

type A = ToArray<string | number>;  // string[] | number[]

⚠️ 坑 1:用 [T] extends ... 包裹可以阻止拆分:

typescript
type ToArrayNonDist<T> = [T] extends unknown ? T[] : never;
type B = ToArrayNonDist<string | number>;  // (string | number)[]

三、infer 关键字(类型推断) ​

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

type A = MyReturnType<() => string>;  // string

// 提取 Promise 内层类型
type PromiseType<T> = T extends Promise<infer U> ? U : never;

type B = PromiseType<Promise<number>>;  // number

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

type C = ElementOf<string[]>;  // string

四、映射类型(Mapped Types) ​

typescript
type Flags<T> = {
  [K in keyof T]: boolean;
};

interface User { id: number; name: string }
type UserFlags = Flags<User>;
// { id: boolean; name: boolean }

修饰符映射:

typescript
// 全部可选
type MyPartial<T> = { [K in keyof T]?: T[K] };

// 全部只读
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

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

// 去掉可选
type Required<T> = { [K in keyof T]-?: T[K] };

⚠️ 坑 2:+ / - 前缀要写在修饰符前面,readonly / ? 关键字前面。

五、as 重映射(TS 4.1+) ​

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

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

type UserGetters = Getters<User>;
// {
//   getId: () => number;
//   getName: () => string;
// }

六、模板字面量类型(Template Literal Types) ​

typescript
type EventName<T extends string> = `on${Capitalize<T>}`;

type ClickEvent = EventName<'click'>;       // 'onClick'
type HoverEvent = EventName<'hover'>;       // 'onHover'

// 配合联合
type AllEvents = EventName<'click' | 'hover' | 'focus'>;
// 'onClick' | 'onHover' | 'onFocus'

内置字符串工具:

typescript
Uppercase<'hello'>;       // 'HELLO'
Lowercase<'HELLO'>;       // 'hello'
Capitalize<'hello'>;      // 'Hello'
Uncapitalize<'Hello'>;    // 'hello'

七、递归类型(实战) ​

typescript
// JSON 类型
type Json =
  | string
  | number
  | boolean
  | null
  | Json[]
  | { [key: string]: Json };

const data: Json = {
  name: 'Tom',
  age: 18,
  tags: ['admin', 'user'],
  meta: { active: true },
};

// 深度 Readonly
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

八、实战:DeepPartial ​

typescript
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Config {
  server: {
    host: string;
    port: number;
    ssl: { cert: string };
  };
}

const cfg: DeepPartial<Config> = {
  server: {
    port: 8080,        // host 可省
    ssl: {},            // cert 可省
  },
};

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

typescript
interface Events {
  login: { userId: string };
  logout: { reason: string };
  click: { x: number; y: number };
}

type EventName = keyof Events;
type EventPayload<T extends EventName> = Events[T];

class Emitter {
  emit<T extends EventName>(event: T, payload: EventPayload<T>): void {
    console.log(`${event}:`, payload);
  }
}

const e = new Emitter();
e.emit('login', { userId: '1' });   // ✅
e.emit('click', { x: 1, y: 2 });    // ✅
e.emit('login', { reason: 'x' });   // ❌ 字段不对

⚠️ 坑 3:高级类型别玩过头,代码可读性下降。团队项目里建议加 ESLint 规则限制复杂度(如 no-restricted-syntax)。

十、本章小结 ​

类型关键
条件类型T extends U ? X : Y,分布式会自动拆分联合
inferT extends Promise<infer U>,提取/解构
映射类型{ [K in keyof T]: ... }
修饰符readonly / ?,前面加 - 去掉
as 重映射as NewKey 改 key 名
模板字面量反引号 + ${} 拼字符串字面量
内置Uppercase / Lowercase / Capitalize / Uncapitalize

动手练习 ​

  1. 条件类型:写 IsNever<T> = [T] extends [never] ? true : false(注意用 [T] 防止分布式)
  2. infer 实战:实现 FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never
  3. 模板字面量:用模板字面量生成 onClick / onHover / onFocus 三种事件名联合

下一章:第 15 章:模块与命名空间 →

本站基于 VitePress 构建 · 由 StackHub 团队维护