Расширяющее соединение типа Generi c не сужается по типу предохранителя - PullRequest
4 голосов
/ 01 марта 2020

Я попытался воспроизвести пример Андерса для условных типов и обобщений, которые он показал на Сборка 2018 (36:45). Он использует условный тип в качестве возвращаемого типа в качестве замены для более традиционных перегрузок функций.

Слайд имеет следующее:

type Name = { name: string };
type Id = { id: number };
type Check = { enabled: boolean };

type LabelForType<T> =
  T extends string ? Name :
  T extends number ? Id :
  T extends boolean ? Check :
  never;

declare function createLabel<T extends string | number | boolean>(value: T): LabelForType<T>

Я попытался немного упростить это и придумал следующий пример. Условный тип возвращает number, когда ему присваивается string, и наоборот, тогда как функция реализует этот условный тип в качестве возвращаемого типа.

type Return<T> = T extends string ? number : T extends number ? string : never;

function typeSwitch<T extends string | number>(x: T):  Return<T>{
  if (typeof x == "string") {
    return 42;
  } else if (typeof x == "number") {
    return "Hello World!";
  }
  throw new Error("Invalid input"); // needed because TS return analysis doesn't currently factor in complete control flow analysis
}

const x = typeSwitch("qwerty"); // number

Однако оба оператора возврата показывают одинаковую ошибку:

Type '42' is not assignable to type 'Return<T>'.(2322)
Type '"Hello World!"' is not assignable to type 'Return<T>'.(2322)

Что мне здесь не хватает?

1 Ответ

4 голосов
/ 01 марта 2020

Вот почему это не работает: Typescript делает сужение типа потока управления для обычных переменных, но не для переменных типа , таких как ваш T. Тип guard typeof x === "string" может использоваться для сужения переменной x до типа string, но он не может сузить T до string и не пытается.

Это имеет смысл, поскольку T может быть типом объединения string | number, даже если x является строкой, поэтому было бы неправильно сузить сам T или сузить верхнюю границу T. Теоретически было бы разумно сузить T до чего-то вроде "тип, который расширяет string | number, но чье пересечение с string не never" , но это добавило бы очень много сложность системы типов для сравнительно небольшого усиления. Не существует полностью общего способа обойти это, кроме как использовать утверждения типа; например, в вашем коде, return 42 as Return<T>;.

Тем не менее, в вашем случае использования вам вообще не нужна функция generi c; Вы можете просто написать две сигнатуры перегрузки :

// overload signatures
function typeSwitch(x: string): number;
function typeSwitch(x: number): string;
// implementation
function typeSwitch(x: string | number): string | number {
  if (typeof x === "string") {
    return 42;
  } else {
    // typeof x === "number" here
    return "Hello World!";
  }
}

Playground Link

...