TypeScript 条件类型
条件类型根据类型之间的关系选择不同的结果类型,写法类似三元表达式,但发生在编译期的「类型层面」,专门用来按条件产出新类型,是高级泛型编程的基石。
基本语法
type IsString<T> = T extends string ? "是字符串" : "不是字符串";
type A = IsString<"hi">; // type A = "是字符串"
type B = IsString<number>; // type B = "不是字符串"
理解它只需记住:T extends U 成立,表示 T 能赋值给 U,即 T 是 U 的子类型。
嵌套与泛型中的使用
条件类型可以嵌套,也可以出现在泛型别名与函数附近:
type Kind<T> = T extends string
? "字符串类"
: T extends number
? "数字类"
: "其他";
type K1 = Kind<string>; // type K1 = "字符串类"
type K2 = Kind<boolean>; // type K2 = "其他"
// 从数组类型中取出元素类型
type ElementType<T> = T extends unknown[] ? T[number] : T;
type E = ElementType<string[]>; // type E = string
条件分支本身还可以是条件类型,从而形成类型层面的 if/else。
分布式条件类型
当 T 是裸类型参数并传入联合类型时,条件会逐个成员分发后再合并结果:
type ToArray<T> = T extends any ? T[] : never;
// 分发:分别处理 number、string
type A = ToArray<number | string>; // type A = number[] | string[]
// 用 [T] 包起来就不分发,整体判断
type ToArray2<T> = [T] extends [any] ? T[] : never;
type B = ToArray2<number | string>; // type B = (number | string)[]
分发特性让条件类型能「逐个过滤联合成员」,Exclude、Extract 都依赖它。
手写 Exclude / NonNullable
理解原理的最好方式是自己实现一遍:
type MyExclude<T, U> = T extends U ? never : T;
type MyNonNullable<T> = T extends null | undefined ? never : T;
// 分发逐个比较:'a' 命中被剔除,'b'、'c' 保留
type R = MyExclude<"a" | "b" | "c", "a">; // type R = "b" | "c"
type S = MyNonNullable<string | null>; // type S = string
被剔除的成员写成 never,联合类型中的 never 会自动消失。
从函数类型中推断
条件类型还能从函数类型里拆出返回类型(infer 细节在后续章节展开):
type MyReturn<T> = T extends (...args: any[]) => infer R ? R : never;
type F = () => number;
type R1 = MyReturn<F>; // type R1 = number
declare function api(id: number): Promise<string>;
type P = MyReturn<typeof api>; // type P = Promise<string>
infer R 表示让 TypeScript 自己去推断出一个类型 R,是很多内置工具的实现基础。
小结:条件类型用 T extends U ? X : Y 在类型层面做判断,裸类型参数的分发机制让它能逐成员处理联合类型,是 Exclude、ReturnType 等工具与 infer 高级用法的地基。