条件类型

在最有用的程序的核心,我们必须根据输入做出决定。JavaScript 程序也不例外,但考虑到值很容易内省,这些决定也基于输入的类型。条件类型有助于描述输入和输出类型之间的关系。

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}

type Example1 = Dog extends Animal ? number : string;

type Example2 = RegExp extends Animal ? number : string;

条件类型的形式看起来有点像 JavaScript 中的条件表达式 (condition ? trueExpression : falseExpression):

type SomeType = any;
type OtherType = any;
type TrueType = any;
type FalseType = any;
type Stuff =
  SomeType extends OtherType ? TrueType : FalseType;

extends 左侧的类型可以分配给右侧的类型时,您将获得第一个分支("true" 分支)中的类型;否则,您将在后一个分支("false" 分支)中获得类型。

从上面的例子中,条件类型可能不会立即有用——我们可以告诉自己是 Dog extends Animal 还是选择 numberstring!但是条件类型的强大之处在于将它们与泛型一起使用。

例如,让我们以下面的 createLabel 函数为例:

interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}

function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

IsKEfnpLn1inAplmkT9S3kkElZm5o7BNmKI9NqAAwJoUjeSKm3EZr1sMpug0+dvybHIHeMLSibno1gGF4VeAs6VTFLBmNxAaBqz0Usx9CWHs/JGFxZhEGZjd8Yf7/JEPL/L8ztAfMd5c2SJaZUoNUR2XxACqJvJkFowXYmWwH5A=

  1. 如果一个库必须在其 API 中一遍又一遍地做出相同的选择,这将变得很麻烦。
  2. 我们必须创建三个重载:一个用于确定类型的每种情况(一个用于 string,一个用于 number),一个用于最一般的情况(采用 string | number)。对于 createLabel 可以处理的每个新类型,重载的数量呈指数增长。

2XMeyBRNdt0kZMdT+CewC0yHrorVJx0pB1124UwTm9PWT2hyAoPgo2BwCOY+SAJj8sK219olcRSlHAXjX0mBuA==

interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

s8fOC2DlyODAaMcDuFhwnGkJk/bpiyOggLLvvW4Zk0/uG1QzKQyRaytRTCGKKEio3BFj3lOgVHBvH2APpt4BYsNcFt47X1j518sk+kSFWyviX7lRIlSoJp2BIlnc//cD

interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  throw "unimplemented";
}

let a = createLabel("typescript");

let b = createLabel(2.8);

let c = createLabel(Math.random() ? "hello" : 42);

条件类型约束

qpMOX1VChFfEAvR8AVsNGrUPtYHfAzceo0FZh7I37ga3kL1u9g5PMQ7QXu8S0NUOelPsufStBAlJ9lrXCivUfGzXZPH1Qimv7tnrabjtF+Cmk5JgvBEroFWAECaJvIwh7lR+2wQwExgh3RmdLssX2ayqD4IeWzrwyL2c4HWPn8k/IjwOz6OBv8Ne8cBcafg4dx3GLlDTsxMHhmrdXBUFlATNjiVbq8UO+7By0/fGvVEwLnNmFmz0DW5NKU8M4xp95ppI5p+3wgebbEIW5XDEhfQy3ECyZGnt5BMyA7Cta+8=

eQYKJ1QpeskDhfY2KH7N8mbtbJ1NMNj1p5jOf9399cCn5QoxwQOvRiHLIX9/EB2w

type MessageOf<T> = T["message"];

5QWVDuHIV2CjtPnzd5EYLks9Vh/dxXbIgBIXz01xt1ANgZ8tYXMl1gKeNUrh4o+v1eZ+l8zFW05ym2cDhkwPQzjTQIqxggd/MAS2tPEC0X4HIYkBp2k0Q23wY5ILnn3coAW8ADp9rCLhW3ZbOno5EG7AtWrz4NRvNEfcnW03T1A47veE6LUvIGZh1dosSNrao0Bh/obMDKR2qj0cbpMMpZ7UrHJ4l0JQJdlfuQd+42xgQ0Jh0RbQA+GvqWjK9wYw

type MessageOf<T extends { message: unknown }> = T["message"];

interface Email {
  message: string;
}

type EmailMessageContents = MessageOf<Email>;

LRTMURNNJ/ZsxZMvHeaeygy9ynBYneUfOPri/0RTjQ9AACtxc98mqcC00jZjFdcJ4R2agLpuw5BXHXN+OLrCQWtxH7DvvWLpyp9EU2uV2Nttq0yNGUo71W4rbjKO1ZPCYvWhvEkxa4NSEohnQ4WtNtb/ffvwexr9iqHpCNiQ1agFLd06D6y9FcbkFC517JqiDqr4TIKFGLMZCMmgrr8LhLkj7td89o5h3N5HxRaBeQpiDiiysmRwmTpgKX3spWxPtDef5dWQebQJqWOA+8eZl8CxnAGaQGeKrhViI/h5M/IYfCFi3Uhp0Z59i2BH2EzpkpuV3GAAn6ezdvKbGTEjwgnjFYDfnfG/LKU7beZI3BY=

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

interface Email {
  message: string;
}

interface Dog {
  bark(): void;
}

type EmailMessageContents = MessageOf<Email>;

type DogMessageContents = MessageOf<Dog>;

5WT106esYyuSvSlxRK9aNWXnCnb+9PQXKM/4U+IU98viIBTTB6FIGOXAa/TH1BdLJwd5a1BFjEgyDxF5AsNBMyeuj6oMWp4J0QZiYh7NvOKPIF3v7/Fe9yFDHopSgwMZ

rRU1RLdJLB7A3BKx9uvX4W2d0GavYwP60SrFnt7LeOIICT8Wm/J0aJB9wJSvlf9DtDsZWf9QuKFV8RSINv2lrePuH05MnEfOehtpQPGox1KbIXDPExbcZn7mu2Jpr8aMg4UpUBTPTHhpByczkXSNwuYXw4an5oZU7B0DMDOpBnmQWN7xzMQAi8b3gq96P9uiCyDS+nsr3bgA0d2UuabEiA==

type Flatten<T> = T extends any[] ? T[number] : T;

// Extracts out the element type.
type Str = Flatten<string[]>;

// Leaves the type alone.
type Num = Flatten<number>;

LWTACxKivgNdesPxvQt+zFdwAi1EHZ7bqGTLp/MXVvdBKO4TYvDl17VV6O11QavpynsttC+6IIEob/fpfFrn+cGzQLrtAJP7QR3fEHwAkahJtr8oO46B3LZPoXuESu5sUFl/l9CuFOhjkchvlkoLeVDFcN+wWrq+B7C/0ks0G+fyVsdVCBNXn9XUyathHkXANKUAq2BZ8uNuLE5/UjkkJSn0B1fpzWjKI4gIgbRaazJAilwu6eHMhhu6hXfwSTp3

在条件类型中推断

m9kHQ/oIbwjTYgLWT6o/b9lNuwnTvRBNblWyxEtEDK9AEF2gIn/SbAjOe2R47ynqUl7TnB9GMlTggBiZEz6mx9Ud/zUwZCS7t8j3SlhpChHSCgly0RLgykWcafQ58mV5RFdZeJVVCeF1cC2QVVpNMaFunj4u6I0/B84NAES62X+xvvkOagKCl7G+4OCSO2J//J6WFq93/63z937+gGQFzg==

JJ4GAa3i9TM9QtwkzQyjc4Zk2NB6xJGk6iKMGPjGr4c3zRswqcBg7Wf8dPD7ef8BxCyYpyay5F3h3Wm5LxvViKh5Qn9fuyKPrpJqwkKQNFF+AD2TpQ5M2T9xAf1FJu1gqg1vAU3kzfFswUY4BkrojmrjnWNwPBFA/eVhSOo7AA3ff9EOH7rB/ZyPl/S2N+Wee/WIUNCkDxfpK9YTT7HI8S6/DcK9KMGmZUazlsRxEubpngxTBijpJ4baxxeuM92snNMQlinNIKIJpNVYwQohy4DGRT1EJP37N0Fsf1cXqPsecINMqUYKDqmX17NT7s+DM70uOii7CVNMYQAWcAFVAIVBU2eYRC5J0kLuoUkagMfzB2Ewh+1UOPJ8VKs0L7K6

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

FtRK/7p9um4giWuKdk8HBy5+JspACxUl82iQjN4xfuq6nYrEDzY96WLBAwx1vv7pe8+Z+VyqhAC7IbbJ2kVQCdZpVWppwIz3Ia75h13yI4EUYqUnQWkkiIGKIIQOvC/GuH4GTdwW7fvZu3vr2HEJkbpr36K/OArkrdllwS077cvpauaYvDzmmQbjb4hStye6IKTuvFEDnffqQzD9iXCY+x4TdCIjwTa9JopFiMdQFG1Z2/kealYVSlnxfQ9aDQJL+03I4nJfptadp/jySka+/TZo0XlIqDBELOfATaSPtXW78FUma0CzF3Oh2pgNZsilX4FLe7kypQvWNISZLnV1lUc2o/EFNQUKj9llg2RacWq+sHY474EcNPhWTQkYXfD5iLbNcFdJZH1a9tEC1S3mdw==

Ve8Z4XqSrYeHYOs94zADcEmcJaseKPqIY2JWb8SgReYvraL4pEm8xZalXlU++loup52MQnA4hThm5PiTBdwlRY/zvTJmljQDgpFO89cs74ToQiUqpycxJMIsZ3QFH+ALIh/GPR0j2ZsqFUstJcrkONvJr7cIeZBLYqOb8UM3o6IV2LgWgCUJLhsgn8i6BQMw0wChoEM6nK0PT7OWQpgvqqmdHhJ8jn3rkifmMTLgF7s=

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;

type Num = GetReturnType<() => number>;

type Str = GetReturnType<(x: string) => string>;

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;

YGIlEt3zMp8aujO7k8VhzRGI9XsRMGVg8MGfzrE/8OmdaixIMHQ++AsNeicYbfX0ly7q78vNvy0y04Ro5iS5SKq2XdqOQedE8J6uVzc2USQbYloJuKA9Musf3Y8A6NxtMy06q37gj31qFdazIW9PIq1sisQI9k+IsnY8QaDScO1a7Aqp+RRJhnDctEd02YfB5+OaymhaO2CZhV+4nX9y+4CK/JkcgHX16x/O6XXxe10ws82Z1poSDYUgMIuABLb9ml5NqeqtW8eooy+P0xEMIPKcmOr22LzG6LLb9wbF/Z06ewOKcAL7rGTRk5Owy/8V

declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;

type T1 = ReturnType<typeof stringOrNum>;

分布式条件类型

bZsP9PuIGAkg7cBiK6ltWUVi8vJe32JrOL/78TeQvDcaOaBtLsHCUzv72/9hTTHju+CscGL8pJADxahhmMgkufzpLaMm5oYWDaQwG3yGrZepvd7cg94/Qsc+DKdv3WMKCjtoVEYXsdjgMGCZAdh+36tWyXrR02aG38VKgUFvAFA=

type ToArray<Type> = Type extends any ? Type[] : never;

qUvCChYAg06qSFs3iLYe6J7ZESVQWMjIZbjiASlBZ8Ga8c37O0D6EGKHDEgOTRPjtPfy34X//I47XoQ/TjwQjSiIBdYh/tij/UeZXX83SMg3EI3HggzsCt5K5RkWMizzUrWFAB2KogEq0rsSQUmFGvjKX9a6ftN5u8cb8IzTt9o=

type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;

LWYfFMKpvZQWJKqgqs141fV8ry0VyEG2tQNCmTh3BYP+xpwobsiKbSEXiZIih6YRdZqVxuNhD/pjfBOQiIIxYA==

type StrArrOrNumArr =
  string | number;

kT38YvFi971+EjeQ7CuG5qHvxIr98hEGbScnk++PniPqFJHYibk4DjhzXkkmcW6Kx4pdqKX2k7dthSCT+z6RPQ==

type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr =
  ToArray<string> | ToArray<number>;

2n7bFr1OOJXiS0Sx3QlwzIvicV60Qm9WtTn1CT1y0h0=

type StrArrOrNumArr =
  string[] | number[];

tpo3MlXrvhTgpNi52dhcpdwe1Jvx/srhyEzoNdp/bVt5CMhpCKE1RX+/g6QvG9UyWv+Yszx4fxXvFMkqsjPxyfAakxlnozeprOpxHyPhj55PVhYT+ixqTrR+4gfqvmrkZC175taeRJCSdA2tGJuyCZTGlsZLuJv6WCpJ8BIM1cXJlhT6+c4IcmieSKvdwK6d

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;