Generi c сопоставления и связывание значений с ключами - PullRequest
0 голосов
/ 09 мая 2020

У меня есть следующий пример (который является упрощением более сложного кода):

type NumMap = {
  "one": 1,
  "two": 2,
  "three": 3,
}

type Proc<M extends { [key: string]: number }> = {
  go<K extends keyof M>(key: K): M[K];
};

const numMapProc: Proc<NumMap> = {
  go: key => {
    if (key === "one") return 1;
    if (key === "two") return 2;
    if (key === "three") return 3;

    throw new Error("whoops");
  }
}

Пробуя это, я получаю следующую ошибку при реализации go():

Type '<K extends "one" | "two" | "three">(key: K) => 1 | 2 | 3' is not assignable to type '<K extends "one" | "two" | "three">(key: K) => NumMap[K]'.
  Type '1 | 2 | 3' is not assignable to type 'NumMap[K]'.
    Type '1' is not assignable to type 'NumMap[K]'.
      Type '1' is not assignable to type 'never'.(2322)

Почему здесь NumMap[K] сокращается до never? Разве K не заблокирован для расширения "one" | "two" | "three"? Есть ли значение для K, которое удовлетворяет ограничению, но индексирует NumMap и дает never?

1 Ответ

1 голос
/ 09 мая 2020

Проблема в том, что компилятор видит функцию

const f = <K extends keyof NumMap>(key: K) => {
  if (key === "one") return 1;
  if (key === "two") return 2;
  if (key === "three") return 3;
  throw new Error("whoops");
}

и может только проверить, что ее возвращаемый тип - 1 | 2 | 3, а не NumMap[K]. Как правило, компилятор не использует анализ потока управления для сужения параметров типа, таких как K. См. microsoft / TypeScript # 13995 . Таким образом, в случае, если key === "one", вы могли бы ожидать, тип key сужаться от K до "one" или K & "one" или что-то, но типа K extends keyof NumMap сам упорно остается такой же и делает не сужаться до чего-то вроде K extends "one". Итак, компилятор недоволен, когда вы используете это вместо того, что принимает NumMap[K].

Конкретная ошибка never вызвана изменением, реализованным в microsoft. / TypeScript # 30769 , чтобы быть более осторожными при попытке присвоить значения переменной индексированного типа, например NumMap[K]. Если K является или может быть объединением, например "one" | "two" | "three", то компилятор разрешит присваивание только в том случае, если значение является пересечением свойств на этих ключах, или в основном NumMap["one"] & NumMap["two"] & NumMap["three"], что является 1 & 2 & 3, который сворачивается в never (потому что никакое значение не может быть 1 и 2 и 3 одновременно). Все это означает, что компилятор обеспокоен тем, что вы получаете 1, 2 или 3, и это не всегда может быть правильным.


Итак, как же сделать мы это исправим? Самый простой способ - использовать утверждение типа , чтобы сообщить компилятору, что вы знаете, что делаете. Это утомительно, но по крайней мере немного безопасно в том смысле, что он, по крайней мере, будет жаловаться, если вы вернете что-то совершенно нестандартное, например 4 или true:

const numMapProcAsserts: Proc<NumMap> = {
  go: <K extends keyof NumMap>(key: K) => {
    if (key === "one") return 1 as NumMap[K];
    if (key === "two") return 2 as NumMap[K];
    if (key === "three") return 3 as NumMap[K];
    throw new Error("whoops");
  }
}

Или вы можете использовать одно утверждение any, чтобы просто заглушить компилятор: заявите, что функция возвращает any, и реализация, и назначение будут счастливы, но не ожидайте, что компилятор поможет вам, если вы измените * От 1054 * до return "oopsie";:

const numMapProcOneAssert: Proc<NumMap> = {
  go: (key): any => {
    if (key === "one") return 1;
    if (key === "two") return 2;
    if (key === "three") return 3;

    throw new Error("whoops");
  }
}

Еще одна идея - провести рефакторинг вашей функции, чтобы компилятор действительно мог следовать логике c того, что вы делаете. Если предполагается, что возвращаемый тип является поисковым / индексированным типом доступа, вы можете выполнить фактический индексированный доступ правильного типа, и компилятор будет доволен. В вашем случае, если вам нужен NumMap[K], покажите компилятору, который вы индексируете, в объект типа NumMap с ключом типа K:

const numMapProcIdx: Proc<NumMap> = {
  go: <K extends keyof NumMap>(key: K) =>
    (({ one: 1, two: 2, three: 3 } as const)[key])
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...