跳到主要内容
预计阅读 35 分钟

类型层面编程:映射类型、条件类型与内置工具

需求场景

订单系统的类型定义越来越多。你发现很多类型之间存在明显的派生关系:

  • Order 有 10 个字段,更新接口只需要传部分字段(所有字段变可选)
  • Order 的列表查询需要一个只包含 orderIdstatustotalAmount 三个字段的精简版
  • 后端返回的所有字段都是只读的,前端表单编辑时需要把只读去掉
  • 事件处理器的名称由事件名自动派生:click 对应 onClicksubmit 对应 onSubmit

手动为每种变体写一遍类型定义既冗长又容易出错。TypeScript 提供了一套在类型层面编程的能力——映射类型、条件类型和 infer 推断——让你可以从已有类型自动派生出新类型。

keyof 运算符

keyof 是类型层面编程的基础工具,它提取对象类型的所有属性名,返回一个联合类型:

type OrderFields = {
  orderId: string;
  customerId: string;
  totalAmount: number;
  status: string;
};

type FieldNames = keyof OrderFields;
// "orderId" | "customerId" | "totalAmount" | "status"

带索引签名的类型中,keyof 返回索引的类型:

interface DynamicMap {
  [prop: string]: number;
}

type MapKeys = keyof DynamicMap; // string | number
// 返回 string | number 是因为 JavaScript 中数字索引本质上也是字符串

keyof 的实际用途

编写类型安全的属性访问函数:

function readField<TObj, TKey extends keyof TObj>(
  source: TObj,
  field: TKey
): TObj[TKey] {
  return source[field];
}

const order = { orderId: "ORD-001", totalAmount: 599, status: "paid" };
const id = readField(order, "orderId");       // string
const amount = readField(order, "totalAmount"); // number
// readField(order, "nonExistent");            // 编译错误

keyof 与联合类型、交叉类型

interface SectionA { title: string; score: number }
interface SectionB { score: number; grade: string }

// 联合类型:keyof 返回共有键
type SharedKeys = keyof (SectionA | SectionB);    // "score"

// 交叉类型:keyof 返回所有键
type AllKeys = keyof (SectionA & SectionB);       // "title" | "score" | "grade"

typeof 在类型层面的使用

在类型上下文中,typeof 从一个运行时值反推出其类型:

const shippingConfig = {
  freeThreshold: 99,
  baseFee: 10,
  expressMultiplier: 1.5,
  regions: ["CN", "HK", "TW"],
};

type ShippingConfig = typeof shippingConfig;
// {
//   freeThreshold: number;
//   baseFee: number;
//   expressMultiplier: number;
//   regions: string[];
// }

typeofkeyof 的经典组合:

const STATUS_MAP = {
  pending: "待处理",
  processing: "处理中",
  completed: "已完成",
  cancelled: "已取消",
};

type StatusCode = keyof typeof STATUS_MAP;
// "pending" | "processing" | "completed" | "cancelled"

as const 数组中提取元素类型:

const supportedCurrencies = ["CNY", "USD", "EUR", "GBP"] as const;
type Currency = (typeof supportedCurrencies)[number];
// "CNY" | "USD" | "EUR" | "GBP"

索引访问类型

用方括号从对象类型中提取属性的值类型:

type OrderDetail = {
  orderId: string;
  amount: number;
  items: {
    sku: string;
    qty: number;
    unitPrice: number;
  }[];
};

type AmountType = OrderDetail["amount"];           // number
type ItemType = OrderDetail["items"][number];       // { sku: string; qty: number; unitPrice: number }
type SkuType = OrderDetail["items"][number]["sku"]; // string

传入联合类型作为索引,一次提取多个属性类型:

type IdOrAmount = OrderDetail["orderId" | "amount"]; // string | number

结合 keyof 获取所有属性值的联合类型:

type AllValueTypes = OrderDetail[keyof OrderDetail];
// string | number | { sku: string; qty: number; unitPrice: number }[]

映射类型

映射类型是类型层面的 map 操作,基于已有类型的结构生成新类型。

基本语法

type MappedType = {
  [Key in Keys]: ValueType;
};

Key 是属性名变量,in 遍历联合类型的每个成员。

示例

复制类型的所有属性:

type OrderSnapshot = {
  orderId: string;
  status: string;
  amount: number;
};

type ClonedSnapshot = {
  [K in keyof OrderSnapshot]: OrderSnapshot[K];
};
// { orderId: string; status: string; amount: number }

统一修改值类型:

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

type OrderFlags = BooleanFlags<OrderSnapshot>;
// { orderId: boolean; status: boolean; amount: boolean }

从联合类型生成对象:

type StatusObject = {
  [S in "pending" | "paid" | "shipped"]: boolean;
};
// { pending: boolean; paid: boolean; shipped: boolean }

映射修饰符

+- 添加或移除 readonly? 修饰符:

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

// 全部变必填(去掉可选标记)
type AllRequired<T> = {
  [K in keyof T]-?: T[K];
};

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

// 全部变可写(去掉只读标记)
type Unfrozen<T> = {
  -readonly [K in keyof T]: T[K];
};

+ 是默认行为可以省略。readonly 等价于 +readonly? 等价于 +?

键重映射(as 子句)

TypeScript 4.1 引入的 as 子句,在映射过程中重命名属性:

type OrderFields = {
  orderId: string;
  status: string;
  amount: number;
};

// 给每个属性名添加后缀
type FieldValidators = {
  [K in keyof OrderFields as `validate${Capitalize<K & string>}`]: () => boolean;
};
// {
//   validateOrderId: () => boolean;
//   validateStatus: () => boolean;
//   validateAmount: () => boolean;
// }

经典的 Getter 生成模式:

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

type OrderGetters = Getters<OrderFields>;
// {
//   getOrderId: () => string;
//   getStatus: () => string;
//   getAmount: () => number;
// }

属性过滤

通过在键重映射中返回 never 过滤属性:

// 只保留值类型为 number 的属性
type NumericFields<T> = {
  [K in keyof T as T[K] extends number ? K : never]: T[K];
};

type OrderNumerics = NumericFields<OrderFields>;
// { amount: number }

条件类型

条件类型是类型层面的三元运算符:

T extends U ? X : Y

如果 T 能赋值给 U,结果为 X,否则为 Y

基础示例

type IsNumeric<T> = T extends number ? true : false;

type A = IsNumeric<42>;     // true
type B = IsNumeric<string>; // false
type C = IsNumeric<3.14>;   // true

分布式条件类型

当条件类型作用于泛型的联合类型参数时,自动对每个成员分别运算:

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

type Result = WrapInArray<string | number>;
// string[] | number[](分别对 string 和 number 应用)

不想要分布式行为时,用方括号包裹:

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

type Result = WrapInArray<string | number>;
// (string | number)[]

过滤联合类型

type FilterOut<T, U> = T extends U ? never : T;

type Remaining = FilterOut<"a" | "b" | "c" | "d", "a" | "c">;
// "b" | "d"

// 分布式展开过程:
// "a" extends "a"|"c" ? never : "a" => never
// "b" extends "a"|"c" ? never : "b" => "b"
// "c" extends "a"|"c" ? never : "c" => never
// "d" extends "a"|"c" ? never : "d" => "d"
// never | "b" | never | "d" => "b" | "d"

infer 关键字

infer 在条件类型中声明待推断的类型变量,让编译器自动推导:

// 提取数组元素类型
type ItemOf<T> = T extends Array<infer Elem> ? Elem : T;

type A = ItemOf<string[]>;   // string
type B = ItemOf<number[]>;   // number
type C = ItemOf<boolean>;    // boolean(非数组,原样返回)

推断函数类型

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

type X = ResultOf<() => string>;          // string
type Y = ResultOf<(a: number) => void>;   // void

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

type Z = ArgsOf<(a: string, b: number) => void>; // [a: string, b: number]

推断模板字面量

type ExtractPath<T> = T extends `${string}://${string}/${infer Path}`
  ? Path
  : never;

type P1 = ExtractPath<"https://api.example.com/orders/list">;
// "orders/list"

模板字面量类型

模板字面量类型在类型层面做字符串拼接:

type Module = "order" | "product" | "user";
type Action = "Create" | "Update" | "Delete";

type EventName = `${Module}${Action}`;
// "orderCreate" | "orderUpdate" | "orderDelete"
// | "productCreate" | "productUpdate" | "productDelete"
// | "userCreate" | "userUpdate" | "userDelete"

联合类型自动做笛卡尔积展开:

type Row = "A" | "B";
type Col = "1" | "2" | "3";
type Cell = `${Row}${Col}`;
// "A1" | "A2" | "A3" | "B1" | "B2" | "B3"

内置工具类型详解

TypeScript 内置了一系列工具类型,都基于映射类型、条件类型和 infer 实现。

Partial

全部属性变可选:

interface OrderDraft {
  customerId: string;
  items: string[];
  coupon: string;
}

type OptionalDraft = Partial<OrderDraft>;
// { customerId?: string; items?: string[]; coupon?: string }

// 实现原理
type Partial<T> = { [K in keyof T]?: T[K] };

典型用途:更新操作只传部分字段。

function updateDraft(id: string, changes: Partial<OrderDraft>): void {
  console.log(`Updating ${id}`, changes);
}
updateDraft("D-001", { coupon: "SAVE20" });

Required

全部属性变必填:

interface OptionalConfig {
  host?: string;
  port?: number;
  debug?: boolean;
}

type FullConfig = Required<OptionalConfig>;
// { host: string; port: number; debug: boolean }

// 实现原理
type Required<T> = { [K in keyof T]-?: T[K] };

Readonly

全部属性变只读:

interface TaskItem {
  title: string;
  done: boolean;
}

type FrozenTask = Readonly<TaskItem>;
// { readonly title: string; readonly done: boolean }

// 实现原理
type Readonly<T> = { readonly [K in keyof T]: T[K] };

Pick<T, K>

选取指定属性:

interface FullOrder {
  orderId: string;
  customerId: string;
  totalAmount: number;
  status: string;
  remark: string;
}

type OrderBrief = Pick<FullOrder, "orderId" | "status" | "totalAmount">;
// { orderId: string; status: string; totalAmount: number }

// 实现原理
type Pick<T, K extends keyof T> = { [P in K]: T[P] };

Omit<T, K>

排除指定属性:

type OrderWithoutRemark = Omit<FullOrder, "remark">;
// { orderId: string; customerId: string; totalAmount: number; status: string }

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

Record<K, T>

构造键值对对象类型:

type StatusLabel = Record<"pending" | "paid" | "shipped", string>;
// {
//   pending: string;
//   paid: string;
//   shipped: string;
// }

// 实现原理
type Record<K extends string | number | symbol, T> = { [P in K]: T };

Exclude<T, U>

从联合类型中排除成员:

type AllStatus = "pending" | "paid" | "shipped" | "cancelled";
type ActiveStatus = Exclude<AllStatus, "cancelled">;
// "pending" | "paid" | "shipped"

// 实现原理
type Exclude<T, U> = T extends U ? never : T;

Extract<T, U>

从联合类型中提取成员:

type TerminalStatus = Extract<AllStatus, "shipped" | "cancelled">;
// "shipped" | "cancelled"

// 实现原理
type Extract<T, U> = T extends U ? T : never;

NonNullable

排除 null 和 undefined:

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string

// 实现原理(TypeScript 4.8+ 的实现方式)
type NonNullable<T> = T & {};

历史注记:TypeScript 4.8 之前,NonNullable 的实现为 T extends null | undefined ? never : T(基于条件类型)。4.8 改用交叉类型 T & {} 后行为更一致——它不再触发分布式条件类型展开,对泛型参数的过滤也更可预测。你在网上看到的很多资料仍在使用旧版实现,但两者在非泛型场景下的结果完全一致。

ReturnType

提取函数返回类型:

function calculateDiscount(price: number, rate: number) {
  return { discounted: price * (1 - rate), saved: price * rate };
}

type DiscountResult = ReturnType<typeof calculateDiscount>;
// { discounted: number; saved: number }

// 实现原理
type ReturnType<T extends (...args: any) => any> =
  T extends (...args: any) => infer R ? R : any;

Parameters

以元组形式返回函数参数类型:

type DiscountArgs = Parameters<typeof calculateDiscount>;
// [price: number, rate: number]

// 实现原理
type Parameters<T extends (...args: any) => any> =
  T extends (...args: infer P) => any ? P : never;

InstanceType

提取构造函数的实例类型:

type ErrInstance = InstanceType<ErrorConstructor>;  // Error
type RegInstance = InstanceType<RegExpConstructor>;  // RegExp

// 实现原理
type InstanceType<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: any) => infer R ? R : any;

字符串操作工具

type U = Uppercase<"hello">;     // "HELLO"
type L = Lowercase<"HELLO">;     // "hello"
type C = Capitalize<"hello">;    // "Hello"
type D = Uncapitalize<"Hello">;  // "hello"

Awaited

递归解包 Promise 的值类型:

type A = Awaited<Promise<string>>;            // string
type B = Awaited<Promise<Promise<number>>>;   // number
type C = Awaited<boolean | Promise<string>>;  // boolean | string

综合实战:自动生成事件处理器类型

interface OrderEventMap {
  submit: { orderId: string; items: string[] };
  cancel: { orderId: string; reason: string };
  refund: { orderId: string; amount: number };
}

// 生成事件处理器方法名
type HandlerName<K extends string> = `on${Capitalize<K>}`;

// 生成完整的事件处理器接口
type EventHandlers<TMap extends Record<string, any>> = {
  [K in keyof TMap as HandlerName<K & string>]: (event: TMap[K]) => void;
};

type OrderHandlers = EventHandlers<OrderEventMap>;
// {
//   onSubmit: (event: { orderId: string; items: string[] }) => void;
//   onCancel: (event: { orderId: string; reason: string }) => void;
//   onRefund: (event: { orderId: string; amount: number }) => void;
// }

// 提取特定事件的数据类型
type SubmitData = OrderEventMap["submit"];
// { orderId: string; items: string[] }

// 只保留包含 orderId 的事件(实际上全部都有,这里演示过滤能力)
type OrderIdEvents<TMap extends Record<string, any>> = {
  [K in keyof TMap as TMap[K] extends { orderId: string } ? K : never]: TMap[K];
};

type FilteredEvents = OrderIdEvents<OrderEventMap>;
// 保留所有三个事件(因为都有 orderId)

本章回顾

概念语法用途
keyofkeyof T提取对象的所有属性名
typeof(类型)typeof value从值反推类型
索引访问T["key"]提取属性的值类型
映射类型[K in Keys]: T从现有类型结构派生出变体类型
映射修饰符+? / -? / +readonly / -readonly添加或移除修饰符
键重映射as NewKey映射时重命名属性名
条件类型T extends U ? X : Y类型层面的三元运算
分布式条件联合类型自动展开对每个成员分别运算
inferinfer R在条件类型中推断类型
模板字面量`${A}${B}`类型层面的字符串拼接
PartialPartial<T>全部属性变可选
RequiredRequired<T>全部属性变必填
ReadonlyReadonly<T>全部属性变只读
Pick / OmitPick<T, K>选取/排除属性
RecordRecord<K, T>构造键值对类型
Exclude / ExtractExclude<T, U>联合类型的过滤
ReturnTypeReturnType<T>提取函数返回类型
ParametersParameters<T>提取函数参数类型

购买课程解锁全部内容

告别类型错误:12 章掌握 TypeScript 工程实战

¥29.90