TypeScript 逻辑运算

条件类型

Copy
type LiteralType<T> = T extends string ? "string" : "other";

type Res1 = LiteralType<"linbudu">; // "string"
type Res2 = LiteralType<599>; // "other"

Copy
export type LiteralType<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends null
? "null"
: T extends undefined
? "undefined"
: never;

type Res1 = LiteralType<"linbudu">; // "string"
type Res2 = LiteralType<599>; // "number"
type Res3 = LiteralType<true>; // "boolean"

条件类型中使用 extends 判断类型的兼容性，而非判断类型的全等性。这是因为在类型层面中，对于能够进行赋值操作的两个变量，我们并不需要它们的类型完全相等，只需要具有兼容性，而两个完全相同的类型，其 extends 自然也是成立的。

Copy
function universalAdd<T extends number | bigint | string>(
x: T,
y: T
): LiteralToPrimitive<T> {
return x + (y as any);
}

export type LiteralToPrimitive<T> = T extends number
? number
: T extends bigint
? bigint
: T extends string
? string
: never;

universalAdd("linbudu", "599"); // string
universalAdd(599, 1); // number
universalAdd(10n, 10n); // bigint

Copy
type Func = (...args: any[]) => any;

type FunctionConditionType<T extends Func> = T extends (
...args: any[]
) => string
? 'A string return func!'
: 'A non-string return func!';

// "A string return func!"
type StringResult = FunctionConditionType<() => string>;
// 'A non-string return func!';
type NonStringResult1 = FunctionConditionType<() => boolean>;
// 'A non-string return func!';
type NonStringResult2 = FunctionConditionType<() => number>;

infer 关键字

TypeScript 中支持通过 infer 关键字来在条件类型中提取类型的某一部分信息，比如上面我们要提取函数返回值类型的话，可以这么放：

Copy
type FunctionReturnType<T extends Func> = T extends (
...args: any[]
) => infer R
? R
: never;

infer 是 inference 的缩写，意为推断，如 infer R 中 R 就表示 待推断的类型。 infer 只能在条件类型中使用，因为我们实际上仍然需要类型结构是一致的，比如上例中类型信息需要是一个函数类型结构，我们才能提取出它的返回值类型。如果连函数类型都不是，那我只会给你一个 never 。

这里的类型结构当然并不局限于函数类型结构，还可以是数组：

Copy
type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T;

type SwapResult1 = Swap<[1, 2]>; // 符合元组结构，首尾元素替换[2, 1]
type SwapResult2 = Swap<[1, 2, 3]>; // 不符合结构，没有发生替换，仍是 [1, 2, 3]

Copy
// 提取首尾两个
type ExtractStartAndEnd<T extends any[]> = T extends [
infer Start,
...any[],
infer End
]
? [Start, End]
: T;

// 调换首尾两个
type SwapStartAndEnd<T extends any[]> = T extends [
infer Start,
...infer Left,
infer End
]
? [End, ...Left, Start]
: T;

// 调换开头两个
type SwapFirstTwo<T extends any[]> = T extends [
infer Start1,
infer Start2,
...infer Left
]
? [Start2, Start1, ...Left]
: T;

Copy
type ArrayItemType<T> = T extends Array<infer ElementType> ? ElementType : never;

type ArrayItemTypeResult1 = ArrayItemType<[]>; // never
type ArrayItemTypeResult2 = ArrayItemType<string[]>; // string
type ArrayItemTypeResult3 = ArrayItemType<[string, number]>; // string | number

Copy
// 提取对象的属性类型
type PropType<T, K extends keyof T> = T extends { [Key in K]: infer R }
? R
: never;

type PropTypeResult1 = PropType<{ name: string }, 'name'>; // string
type PropTypeResult2 = PropType<{ name: string; age: number }, 'name' | 'age'>; // string | number

// 反转键名与键值
type ReverseKeyValue<T extends Record<string, unknown>> = T extends Record<infer K, infer V> ? Record<V & string, K> : never

type ReverseKeyValueResult1 = ReverseKeyValue<{ "key": "value" }>; // { "value": "key" }

Copy
type PromiseValue<T> = T extends Promise<infer V> ? PromiseValue<V> : T;

type PromiseValueResult1 = PromiseValue<Promise<number>>; // number
type PromiseValueResult2 = PromiseValue<number>; // number，但并没有发生提取

分布式条件类型 (Distributive Conditional Type)

对于属于裸类型参数的检查类型，条件类型会在实例化时期自动分发到联合类型上。(是否通过泛型参数传入)

Copy
type Condition<T> = T extends 1 | 2 | 3 ? T : never;

// 1 | 2 | 3
type Res1 = Condition<1 | 2 | 3 | 4 | 5>;

// never
type Res2 = 1 | 2 | 3 | 4 | 5 extends 1 | 2 | 3 ? 1 | 2 | 3 | 4 | 5 : never;

Copy
type Naked<T> = T extends boolean ? "Y" : "N";
type Wrapped<T> = [T] extends [boolean] ? "Y" : "N";

// "N" | "Y"
type Res3 = Naked<number | boolean>;

// "N"
type Res4 = Wrapped<number | boolean>;

把上面的线索理一下，其实我们就大致得到了条件类型分布式起作用的条件。首先，你的类型参数需要是一个联合类型 。其次，类型参数需要通过泛型参数的方式传入，而不能直接进行条件类型判断（如 Res2 中）。最后，条件类型中的泛型参数不能被包裹。

而条件类型分布式特性会产生的效果也很明显了，即将这个联合类型拆开来，每个分支分别进行一次条件类型判断，再将最后的结果合并起来（如 Naked 中）。

而这里的裸类型参数，其实指的就是泛型参数是否完全裸露，我们上面使用数组包裹泛型参数只是其中一种方式，比如还可以这么做：

Copy
export type NoDistribute<T> = T & {};

type Wrapped<T> = NoDistribute<T> extends boolean ? "Y" : "N";

type Res1 = Wrapped<number | boolean>; // "N"
type Res2 = Wrapped<true | false>; // "Y"
type Res3 = Wrapped<true | false | 599>; // "N"

需要注意的是，我们并不是只会通过裸露泛型参数，来确保分布式特性能够发生。在某些情况下，我们也会需要包裹泛型参数来禁用掉分布式特性。最常见的场景也许还是联合类型的判断，即我们不希望进行联合类型成员的分布判断，而是希望直接判断这两个联合类型的兼容性判断，就像在最初的 Res2 中那样：

Copy
type CompareUnion<T, U> = [T] extends [U] ? true : false;

type CompareRes1 = CompareUnion<1 | 2, 1 | 2 | 3>; // true
type CompareRes2 = CompareUnion<1 | 2, 1>; // false

另外一种情况则是，当我们想判断一个类型是否为 never 时，也可以通过类似的手段：

Copy
type IsNever<T> = [T] extends [never] ? true : false;

type IsNeverRes1 = IsNever<never>; // true
type IsNeverRes2 = IsNever<"linbudu">; // false

Copy
type IsAny<T> = 0 extends 1 & T ? true : false;

交叉类型就像短板效应一样，其最终计算的类型是由最短的那根木板，也就是最精确的那个类型决定的

Copy
type IsUnknown<T> = unknown extends T
? IsAny<T> extends true
? false
: true
: false;

需要注意的是这里的 never 与 any 的情况并不完全相同，any 在直接作为判断参数时、作为泛型参数时都会产生这一效果：

Copy
// 直接使用，返回联合类型
type Tmp1 = any extends string ? 1 : 2; // 1 | 2

type Tmp2<T> = T extends string ? 1 : 2;
// 通过泛型参数传入，同样返回联合类型
type Tmp2Res = Tmp2<any>; // 1 | 2

// 如果判断条件是 any，那么仍然会进行判断
type Special1 = any extends any ? 1 : 2; // 1
type Special2<T> = T extends any ? 1 : 2;
type Special2Res = Special2<any>; // 1

而 never 仅在作为泛型参数时才会产生：

Copy
// 直接使用，仍然会进行判断
type Tmp3 = never extends string ? 1 : 2; // 1

type Tmp4<T> = T extends string ? 1 : 2;
// 通过泛型参数传入，会跳过判断
type Tmp4Res = Tmp4<never>; // never

// 如果判断条件是 never，还是仅在作为泛型参数时才跳过判断
type Special3 = never extends never ? 1 : 2; // 1
type Special4<T> = T extends never ? 1 : 2;
type Special4Res = Special4<never>; // never