条件类型的使用
条件类型的使用
一句话概括
条件类型就是类型系统里的 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" 把不符合条件的属性从结果中删除
「其实你每天都在用」
- 内置工具类型全部基于条件类型:
Partial靠映射类型,Exclude靠分发条件类型,ReturnType靠 infer - API 响应类型提取:
ApiResponse<T>的data字段路由到不同业务类型 - Vue/React 事件处理:
emit的类型会根据事件名自动推导 payload 类型 - 表单验证:字段类型不同,校验规则类型也不同,靠条件类型关联
- 第三方库的类型声明:
Parameters<typeof fn>提取函数参数类型写测试/封装
常见误解(FAQ)
❌ 误区 1:extends 就是”继承”
在 interface Dog extends Animal 里是继承;在条件类型 T extends U 里是类型兼容性检查——T 可否赋值给 U。两处同一个关键字,完全不同的语义。
❌ 误区 2:条件类型的分发一定发生
只有裸类型参数才触发分发。[T] extends ... 包装后不再分发,T extends never 对 never 也不分发(因为 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 U 加 infer 的组合拳,让你能在类型层面做正则匹配级别的计算。
本文由作者按照 CC BY 4.0 进行授权