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 高级用法的地基。

笔记加载中…