文章

条件类型的使用

条件类型的使用

一句话概括

条件类型就是类型系统里的 if-else——T extends U ? X : Y,写一行就能根据输入类型动态决定输出类型,是所有内置工具类型和类型体操的基石。

核心知识点

1. 基础语法:类型三元表达式

1
2
3
4
5
type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<"hello">; // "yes"(字面量是 string 子类型)
type B = IsString<number>;  // "no"
type C = IsString<any>;     // "yes" | "no" ← any 的特殊行为

2. 分发条件类型——最核心也最容易踩坑

当泛型是裸类型参数且传入联合类型时,TS 会对每个成员分别判断再取联合

1
2
3
4
5
6
7
8
9
type ToArray<T> = T extends any ? T[] : never;
// 当 T = string | number:
//   string extends any → string[]
//   number extends any → number[]
// 结果:string[] | number[]  ← 不是 (string | number)[]!

// 阻止分发:用元组包裹
type ToArrayNoDist<T> = [T] extends [any] ? T[] : never;
type R = ToArrayNoDist<string | number>; // (string | number)[] ✅

3. infer:在条件类型中”抓取”类型片段

1
2
3
4
5
6
7
8
9
10
// 提取函数返回值——面试必手写
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

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

// 递归解包 Promise
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;
type D = Awaited<Promise<Promise<number>>>; // number

4. Exclude / Extract:分发条件类型的经典应用

1
2
3
4
5
6
type MyExclude<T, U> = T extends U ? never : T;
type Result = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"

type MyExtract<T, U> = T extends U ? T : never;
// 原理:分发后,"a" extends "a" → "a"、"b" extends "a" → never、"c" extends "a" → never
//       结果:never | "a" | never = "a"

5. 条件类型 + 映射类型 = 属性过滤

1
2
3
4
5
6
7
8
9
10
11
12
// 只保留函数类型的属性
type FunctionProps<T> = {
  [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

interface Svc {
  start(): void;
  stop(): void;
  port: number;
}
type Methods = FunctionProps<Svc>; // { start: () => void; stop: () => void }
// "as never" 把不符合条件的属性从结果中删除

「其实你每天都在用」

  1. 内置工具类型全部基于条件类型Partial 靠映射类型,Exclude 靠分发条件类型,ReturnType 靠 infer
  2. API 响应类型提取ApiResponse<T>data 字段路由到不同业务类型
  3. Vue/React 事件处理emit 的类型会根据事件名自动推导 payload 类型
  4. 表单验证:字段类型不同,校验规则类型也不同,靠条件类型关联
  5. 第三方库的类型声明Parameters<typeof fn> 提取函数参数类型写测试/封装

常见误解(FAQ)

❌ 误区 1:extends 就是”继承”

interface Dog extends Animal 里是继承;在条件类型 T extends U 里是类型兼容性检查——T 可否赋值给 U。两处同一个关键字,完全不同的语义。

❌ 误区 2:条件类型的分发一定发生

只有裸类型参数才触发分发。[T] extends ... 包装后不再分发,T extends nevernever 也不分发(因为 never 是空联合,没有成员可以遍历)。

❌ 误区 3:IsNever<T> 直接写 T extends never ? true : false 就行

不对!T extends never 遇到 never 时,由于 never 是空联合类型,分发后没有任何成员,结果是 never 而不是 true。正确写法:[T] extends [never] ? true : false

❌ 误区 4:infer 只用于提取,不能用于约束

infer 后可以加约束:T extends Promise<infer V extends string> 等价于要求 V 满足 string。TS 4.7+ 支持这个语法,面试可以加分。

一句话总结

条件类型是 TypeScript 从”静态标注”到”类型编程”的分水岭——T extends Uinfer 的组合拳,让你能在类型层面做正则匹配级别的计算。

本文由作者按照 CC BY 4.0 进行授权