Desmond

Desmond

An introvert who loves web programming, graphic design and guitar
github
bilibili
twitter

TypeScript 論理演算

条件型#

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

type Res1 = LiteralType<"linbudu">; // "string"
type Res2 = LiteralType<599>; // "other"
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 自然也是成立的。

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
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 关键字来在条件类型中提取类型的某一部分信息,比如上面我们要提取函数返回值类型的话,可以这么放:

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

infer は inference の略で、推論を意味します。 infer R の中の R は推論される型を表します。 infer は条件型の中でのみ使用でき、実際には型構造が一致している必要があります。例えば、上記の例では型情報が関数型構造である必要があり、そうでなければ never を返します。

ここでの型構造は関数型構造に限らず、配列でも可能です:

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]
// 先頭と末尾の2つを抽出
type ExtractStartAndEnd<T extends any[]> = T extends [
  infer Start,
  ...any[],
  infer End
]
  ? [Start, End]
  : T;

// 先頭と末尾の2つを入れ替え
type SwapStartAndEnd<T extends any[]> = T extends [
  infer Start,
  ...infer Left,
  infer End
]
  ? [End, ...Left, Start]
  : T;

// 先頭の2つを入れ替え
type SwapFirstTwo<T extends any[]> = T extends [
  infer Start1,
  infer Start2,
  ...infer Left
]
  ? [Start2, Start1, ...Left]
  : T;
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
// オブジェクトのプロパティ型を抽出
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" }
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)#

裸の型パラメータに対する条件型は、インスタンス化の時点で自動的にユニオン型に分配されます。(ジェネリックパラメータを通じて渡されるかどうか)

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;
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 のように)。

ここでの裸の型パラメータは、ジェネリックパラメータが完全に裸であるかどうかを指します。上記のように配列でジェネリックパラメータを包むのはその一例です。例えば、次のようにすることもできます:

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 のように、これらの 2 つのユニオン型の互換性を直接判断したい場合です:

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 であるかどうかを判断したい場合でも、同様の手段を使用できます:

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

type IsNeverRes1 = IsNever<never>; // true
type IsNeverRes2 = IsNever<"linbudu">; // false
type IsAny<T> = 0 extends 1 & T ? true : false;

交差型は短板効果のように、最終的に計算される型は最も短い板、つまり最も正確な型によって決まります。

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

ここでの never と any の状況は完全に同じではないことに注意してください。any は直接判断パラメータとしてジェネリックパラメータとして使用されるとこの効果を生じます:

// 直接使用、ユニオン型を返す
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 はジェネリックパラメータとしてのみ効果を発揮します:

// 直接使用、依然として判断が行われる
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

References:
https://juejin.cn/book/7086408430491172901

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。