Объединение строк TypeScript, не назначаемое объединению кортежей в функции

Поведение этих двух примеров должно быть идентичным, но второй ошибка. Почему?

// Example 1:
const a: 'x' | 'y' = 'x'; 
const b: ['x'] | ['y'] = [a]; // ok

// Example 2:
function fn(a: 'x' | 'y') {
  const b: ['x'] | ['y'] = [a];
  //    ^
  // Type '["x" | "y"]' is not assignable to type '["x"] | ["y"]'.
  //   Type '["x" | "y"]' is not assignable to type '["x"]'.
  //     Type '"x" | "y"' is not assignable to type '"x"'.
  //       Type '"y"' is not assignable to type '"x"'.
}

Вы можете попробуй на детской площадке.

Я думаю, что 'x' | 'y' будет более естественно отображаться на ['x' | 'y'], и это действительно работает: const b: ['x' | 'y'] = [a];

crashmstr 16.05.2019 20:44
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
1
908
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

ОБНОВЛЕНИЕ: 30 мая 2019 г. в выпуске TypeScript 3.5 представлен более умная проверка типа объединения, который исправляет это для типов объектов (например, {a: "x"} | {a: "y"}, но, похоже, ничего не делает с типами кортежей (например, ["x"] | ["y"]). Не уверен, намеренно это или нет.


В «Примере 1» тот факт, что a инициализируется как "x", имеет большое значение. Анализ потока управления сужает тип a до "x", несмотря на вашу аннотацию как "x" | "y":

let a: "x" | "y" = "x";
console.info(a === "y"); // error!
// This condition will always return 'false' 
// since the types '"x"' and '"y"' have no overlap.

Тогда, конечно, в этом случае [a] будет соответствовать ["x"] | ["y"], поскольку компилятор знает, что [a] имеет тип ["x"].


Следовательно, пример 1 удается только случайно. В общем, это не удается. Компилятор обычно не считает [A] | [B] эквивалентом [A | B]. Первый рассматривается как строго более узкий тип, чем второй.

type Extends<T, U extends T> = true;
type OkayTup = Extends<[string | number], [string] | [number]>; 
type NotOkayTup = Extends<[string] | [number], [string | number]>; // error!

Это может показаться удивительным, поскольку на самом деле каждое значение типа [A | B] должно присваиваться типу [A] | [B]. Тот же самый сюрприз происходит, когда вы смотрите на аналогичную версию с пакетом свойств:

type OkayObj = Extends<{a: string | number}, {a: string} | {a: number}>;
type NotOkayObj = Extends<{a: string} | {a: number}, {a: string | number}>; // error!

Опять же, {a: A} | {a: B} считается строго более узким типом, чем {a: A | B}, несмотря на то, что вам будет трудно придумать значение последнего типа, которое нельзя было бы присвоить первому.

Итак, что здесь происходит? Что ж, кажется, это либо преднамеренный, либо конструктивное ограничение TypeScript. Архитектор Word of Language говорит:

For your example to type check with no errors we would have to consider types of the form { x: "foo" | "bar" } to be equivalent to { x: "foo" } | { x: "bar" }. But this sort of equivalence only holds for types with a single property and isn't true in the general case. For example, it wouldn't be correct to consider { x: "foo" | "bar", y: string | number } to be equivalent to { x: "foo", y: string } | { x: "bar", y: number } because the first form allows all four combinations whereas the second form only allows two specific ones.

(Примечание: эквивалентность выполняется в несколько большем количестве случаев, чем указано выше... она применяется только в том случае, если свойства, которые являются разные в каждой составляющей объединения, принимают все возможные значения объединения в случае с одним свойством. Итак, {x: string | number, y: boolean, z: string} эквивалентно {x: string, y: true, z: string} | {x: string, y: false, z: string} | {x: number, y: true, z: string} | {x: number, y: false, z: string})

Я бы сказал, что это ограничение дизайна... обнаружение относительно редких случаев, когда союзы свойств могут быть свернуты/расширены, было бы очень дорого, и его просто не стоит реализовывать.


На практике, если вы столкнулись с объединением свойств объединения, которое компилятор не проверяет, но которое, как вы знаете, безопасно, продемонстрируйте свой превосходный интеллект и утверждать свой выход из этого:

function fn(a: 'x' | 'y') {
  const b = [a] as ['x'] | ['y'] // I'm smarter than the compiler ? 
}

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

Другие вопросы по теме