Почему [1 | 2] не является подтипом [1 | []] | [2 | []] в TypeScript?

В TypeScript у меня есть API, входные данные которого представляют собой объединение кортежей, и у меня есть значение, тип которого представляет собой кортеж объединений, который я хочу передать в него. Кажется, что это работает в большинстве случаев, но я не могу понять, почему это не работает в моем случае и что я могу сделать, чтобы заставить его работать без необходимости утверждения типа. Вот репродукция выпуска (ссылка TS Playground):

(x: [1 | 2]): [1] | [2]  => x; // OK
(x: [1 | 2]): [1 | 11] | [2 | 22]  => x; // OK
(x: [1 | 2]): [1 | []] | [2 | 22]  => x; // OK
(x: [1 | 2]): [1 | []] | [2 | []]  => x; // ERROR

Несмотря на то, что первые три примера в порядке, четвертый пример выдает ошибку:

Type '[1 | 2]' is not assignable to type '[1 | []] | [2 | []]'.
  Type '[1 | 2]' is not assignable to type '[1 | []]'.
    Type '1 | 2' is not assignable to type '1 | []'.
      Type '2' is not assignable to type '1 | []'.

Все это будут ошибки в TS3.4 и ниже. Вообще говоря, {a: X | Y} нельзя назначить {a: X} | {a: Y}. В TS3.5 введена так называемая «более умная» проверка типов объединения, которая пытается разрешить такие вещи, но только для распознаваемых объединений, то есть пытается только в том случае, если свойство X или Y является литеральным типом. Поэтому 22 сработает, а [] нет. Это полностью решает вопрос? если да, то я напишу а; если нет, то что мне не хватает?

jcalz 29.06.2024 17:36

Хм, я так думаю — разве кортежи не ковариантны, как массивы?

vijrox 30.06.2024 01:56

Кортежи действительно ковариантны; не могли бы вы объяснить, почему это имеет значение? Вы можете заменить {a: X | Y} на [X | Y] и {a: X} | {a: Y} на [X] | [Y], и это тот же аргумент. TypeScript обычно не пытается выполнять манипулирование типами, связанное с распределением объединений между произвольными ковариантными операторами типов.

jcalz 30.06.2024 02:03

Моя ошибка, я думаю, что перепутал здесь ковариацию и контравариантность. Если бы кортежи были контравариантными, то [X | Y] вообще можно было бы присвоить [X] | [Y], поскольку первый можно было бы присвоить каждому элементу объединения во втором. Я понимаю; Я не знал, что TypeScript предназначен для такой несогласованности распределения объединений в некоторых случаях, а не в других подобных случаях.

vijrox 30.06.2024 02:12
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
4
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вообще говоря, TypeScript не распределяет автоматически все операторы ковариантного типа по типам-объединениям. Итак, {a: X | Y} не становится {a: X} | {a: Y}, а [X | Y] не становится [X] | [Y]. Выполнение чего-то подобного в целом вызвало бы взрыв типов объединений (представьте, чем бы стал [X | Y, T | U | V | W, Q | R | S]). Он также не определяет автоматически, будут ли два типа совместимы, если каждый из них будет распространяться таким образом. Компилятору придется проделать большую работу, но польза от этого будет ограничена. До TypeScript 3.5 все ваши примеры были ошибками.

В TypeScript 3.5 введена так называемая «умная» проверка типов объединения , реализованная в microsoft/TypeScript#30779 , в которой вышеупомянутое распределение действительно имеет место, но только если рассматриваемые типы объединения являются различаемыми объединениями и только в том случае, если рассматриваемые профсоюзы будут иметь 25 или менее членов. Эти ограничения накладывают ограничения на объем дополнительной работы, которую TypeScript необходимо затрачивать для проверки этих вещей, но также приводят к поведению, которое, по мнению разработчиков, может быть непоследовательным или произвольным.

Итак, рассматривая

(x: [1 | 2]): [1] | [2] => x; // OK
(x: [1 | 2]): [1 | 11] | [2 | 22] => x; // OK
(x: [1 | 2]): [1 | []] | [2 | 22] => x; // OK
(x: [1 | 2]): [1 | []] | [2 | []] => x; // ERROR

В первых трех случаях типы объединения [1] | [2], [1 | 11] | [2 | 22] и [1 | []] | [2 | 22] считаются распознаваемыми объединениями. Элемент кортеж (свойство по индексу "0") представляет собой литеральный тип или объединение литеральных типов хотя бы в одном члене объединения. Но тип [1 | []] | [2 | []] не считается распознаваемым объединением. Пустой тип кортежа [] не считается литеральным типом, и поскольку он существует в каждом члене объединения и ни один член объединения не состоит только из литеральных типов, он не соответствует критериям.

Это означает, что «умная» проверка типа объединения происходит в первых трех примерах, но не происходит в последнем примере.

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