Я столкнулся со следующей задачей машинописного текста:
Не имея возможности найти решение самостоятельно, я искал самое популярное решение:
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, но здесь, похоже, это не так. Есть какие-нибудь подсказки о том, что происходит?
Это довольно близко. Как вы уже предположили, если 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!
Ой, извини за это. Большинство операторов типов в TS естественным образом дистрибутивны по объединениям. Если вы спрашиваете, как [X, ...([Y]|[Z])] становится [X, Y] | [X, Z], то именно так ведут себя вариационные кортежи, как это реализовано в ms/TS#39094 (см. часть, в которой говорится «когда аргумент типа для T является типом объединения»). Теперь это полностью решает проблему?
ааа! Спасибо @jcalz! Это именно то, что я искал! Определенно пропустил это! Извиняюсь, если я не был достаточно ясен ранее. Если вы не возражаете, пожалуйста, дайте правильный ответ, и я приму его, чтобы этот вопрос остался в записи 🙏






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