Дистрибутивность сопоставленных типов

Я столкнулся со следующей задачей машинописного текста:

https://github.com/type-challenges/type-challenges/blob/main/questions/15260-hard-tree-path-array/README.md

Не имея возможности найти решение самостоятельно, я искал самое популярное решение:

type Path<T> = T extends Record<PropertyKey, unknown>
  ? {
      [P in keyof T]: [P, ...Path<T[P]>] | [P];
    }[keyof T]
  : never;

Это прекрасно работает, но для меня это не имеет смысла. Что меня здесь беспокоит, так это то, что если мы проверим решение с помощью таких входных данных:

type PathTest2 = Path<{ bar: { a: string }; baz: { b: number; c: number } }>;

мы получаем это:

['bar'] | ['baz'] | ['bar', 'a'] | ['baz', 'b'] | ['baz', 'c']

... и я изо всех сил пытаюсь понять, как работает дистрибутивность, когда мы дошли до этого момента:

{ baz: ['baz', ...['b'] | ['c']] | ['baz'] }

Я бы понял, если бы мы передали союз Path, но здесь, похоже, это не так. Есть какие-нибудь подсказки о том, что происходит?

Дистрибутивность, которая здесь имеет значение, заключается в том, что здесь используется тип дистрибутивного объекта, как это описано в ms/TS#47109. Если у вас есть F<K>, то {[P in K]: F<P>}[K] распределит F по союзам в K. Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 08.06.2024 20:12

Это довольно близко. Как вы уже предположили, если K является союзом, то вполне логично, что F распределяется по этому союзу K. Мой случай немного отличается (я думаю): Итак, если входные данные такие: { bar: { a: string }; baz: { b: number; c: number } }, то, когда запись baz будет обработана, мы придем к этому: {baz: ['baz', ...Path<{b: number, c: number}>] | ['baz']} что приведет к следующему: {baz: ['baz', ...['b']|['c']] | ['baz']} . Теперь мой вопрос заключается в том, как ['b']|['c'] распределяется по кортежу, чтобы получить: { baz: ['baz', 'b'] | ['baz', 'c'] | 'baz' }. Хотелось бы получить полный ответ @jcalz!

stratis 10.06.2024 09:17

Ой, извини за это. Большинство операторов типов в TS естественным образом дистрибутивны по объединениям. Если вы спрашиваете, как [X, ...([Y]|[Z])] становится [X, Y] | [X, Z], то именно так ведут себя вариационные кортежи, как это реализовано в ms/TS#39094 (см. часть, в которой говорится «когда аргумент типа для T является типом объединения»). Теперь это полностью решает проблему?

jcalz 10.06.2024 14:01

ааа! Спасибо @jcalz! Это именно то, что я искал! Определенно пропустил это! Извиняюсь, если я не был достаточно ясен ранее. Если вы не возражаете, пожалуйста, дайте правильный ответ, и я приму его, чтобы этот вопрос остался в записи 🙏

stratis 10.06.2024 16:46
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
4
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Многие (но не все) операторы типов в TypeScript естественным образом дистрибутивны по отношению к объединениям, поскольку многие операции естественно ковариантны (см. Разница между дисперсией, ковариантностью, контравариантностью, бивариантностью и инвариантностью в TypeScript).

Например, индексированные типы доступа являются распределительными по объединениям в ключе (например, T[K1 | K2] — это T[K1] | T[K2]). Существуют также распределительные условные типы.

Оказывается, вариативные типы кортежей также являются дистрибутивными по отношению к объединениям в распространяемом типе массива/кортежа. Итак, если у вас есть [A, B, C, ...(T1 | T2), X, Y, Z], это преобразуется в [A, B, C, ...T1, X, Y, Z] | [A, B, C, ...T2, X, Y, Z]. Это описано в microsoft/TypeScript#39094, запросе на включение, в котором реализованы типы кортежей с переменным числом вариантов:

Создание экземпляра типа кортежа с переменным элементом ...T зависит от аргумента типа, предусмотренного для T [...] Когда аргумент типа для T является типом объединения, объединение распространяется на тип кортежа. Например, экземпляр [A, ...T, B], созданный с помощью X | Y | Z в качестве аргумента типа для T, дает объединение экземпляров [A, ...T, B] с X, Y и Z в качестве аргумента типа для T соответственно.

Таким образом, ['baz', ...['b'] | ['c']] автоматически преобразуется в ['baz', 'b'] | ['baz', 'c'].

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