Как сказать машинописи, что я правильно использую условный тип над объединением? - PullRequest
0 голосов
/ 29 января 2020

У меня есть этот базовый случай:

type Ids = "foo" | "bar" | "bazz";

interface IFoo {
  foo: string;
}

interface IBar {
  bar: number;
}

interface IBazz {
  bazz: boolean;
}

type Ret<T extends Ids> = T extends "foo" ? IFoo :
  T extends "bar" ? IBar :
  T extends "bazz" ? IBazz :
  never;

function a<T extends Ids>(id: T): Ret<T> {
  switch (id) {
    case "bar":
      return { bar: 1 };
    case "foo":
      return { foo: "foo" };
    case "bazz":
      return { bazz: true };
  }
}

const bar: IBar = a("bar");
const foo: IFoo = a("foo");
const bazz: IBazz = a("bazz");

Как видите, Typescript не удовлетворен моей реализацией функции a. Что я должен изменить, чтобы скомпилировать эту функцию, но по-прежнему держать гарантии в течение последних трех утверждений

1006 * площадка: https://www.typescriptlang.org/play/index.html#code / C4TwDgpgBAkgJgZygXigIgGYHstqgH3QCMBDAJz0LVIC8a0BuAKCYEsA7YCMjEgY2gwAYjigBvJlCjYsALigJgZDgHNmAXxYcuPfoIBC5cZKiky89gFcAtkW4atnbrwGxDdY1No15RHABsIEnYHJlBIKAAlCGAAHgAVKAgADy52RFhEAD4UKESUtIzMHDwAflgRLChZE3zUiHSkanIytyMaqTrCpu9WmHcaapN2CAA3exYMS3Y + YFYsdigSBKT6xsyELIAKVjh5eIBKeWi4 + JyJKQQAd1ZgPgALKB24A88pKD4SBGhmig73qRkGKWMiLMSmcjyACMUHUzABn2 + 6BkaH + AKBwBBYOkOHkxVwsPh70RP16aPeGKx4ghdHkSks0DhJk0miYfAWigh5jaZFyJC2vzQB2Y7PYnJk8mEolQ-PxQpFHOANJ8bQ8MoFvWFQA

1 Ответ

1 голос
/ 29 января 2020

Это открытый вопрос в TypeScript (см. Microsoft / TypeScript # 33912) , что компилятор, как правило, не может проверить, соответствует ли возвращаемое значение конкретной функции условному типу, который зависит от пока еще неопределенный параметр типа generi c, например Ret<T> внутри реализации a(), где T не разрешена. Это связано с тем, что TypeScript не может сузить параметры ввода с помощью анализа потока управления (см. Microsoft / TypeScript # 13995) , поэтому проверка id с помощью оператора switch / case может сузить тип id, скажем, "bar", но он не сужает параметр типа T до "bar", и, таким образом, он не может гарантировать, что Ret<"bar"> является приемлемым вывод.


Одна вещь, которую вы можете сделать, это принять, что компилятор не может проверить это для вас, и использовать утверждения типа или перегрузку , чтобы ослабить Реализация набирает достаточно, чтобы избежать ошибок. Это будет работать, но компилятор не гарантирует безопасность типов. Например, с перегрузкой:

function aOverload<T extends Ids>(id: T): Ret<T>;
function aOverload(id: Ids): Ret<Ids> {
  switch (id) {
    case "bar":
      return { bar: 1 };
    case "foo":
      return { foo: "foo" };
    case "bazz":
      return { bazz: true };
  }
}

теперь нет ошибок, и есть некоторая безопасность типов ... вы не можете вернуть совершенно неправильный тип, такой как {spazz: true}, но вы можете поменять местами case, и он не заметит:

function aBadOverload<T extends Ids>(id: T): Ret<T>;
function aBadOverload(id: Ids): Ret<Ids> {
  switch (id) {
    case "bazz":
      return { bar: 1 };
    case "bar":
      return { foo: "foo" };
    case "foo":
      return { bazz: true };
  }
}

Поэтому вы должны быть осторожны.


Еще одно решение этого конкретного случая - отказаться от условные типы в пользу индексации generi c, например:

interface RetMap {
  foo: IFoo,
  bar: IBar,
  bazz: IBazz;
}

function aGood<K extends keyof RetMap>(id: K): RetMap[K] {
  return {
    bar: { bar: 1 },
    foo: { foo: "foo" },
    bazz: { bazz: true }
  }[id];
}

const bar: IBar = aGood("bar");
const foo: IFoo = aGood("foo");
const bazz: IBazz = aGood("bazz");

Компилятор может проверить, что то, что мы здесь делаем, безопасно, потому что мы индексируем объект типа RetMap с помощью клавиши id типа K. Да, и если вы недовольны тем, что эта версия предварительно вычисляет возвращаемые значения, которые она не будет использовать, вы можете изменить рефакторинг для использования getters , что также устраивает компилятор:

function aGood<K extends keyof RetMap>(id: K): RetMap[K] {
  return {
    get bar() { return { bar: 1 } },
    get foo() { return { foo: "foo" } },
    get bazz() { return { bazz: true } }
  }[id];
}

Хорошо, надеюсь, это поможет вам продолжить; удачи!

Детская площадка ссылка на код

...