TypeScript условно сопоставляет необязательные элементы кортежа - PullRequest
1 голос
/ 16 июня 2020

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

type Input = [string, number | undefined]

type UndefinedToOptional<T> = { [K in keyof T}: T[K] extends undefined ? ... : ... } // ???

type Output = UndefinedToOptional<Input> // should be [string, (number | undefined)?]

Я могу создать сопоставленный тип, который всегда добавляет необязательный модификатор:

type ToOptional<T> = { [K in keyof T]+?: T[K] }

type AllOptionalOutput = ToOptional<Input> // this is now [string?, (number | undefined)?]

Но я не уверен, как сделать необязательный модификатор условный. Для сопоставленных типов, работающих с объектами, я бы sh сделал это, создав два типа объектов и пересекая их, где все свойства устанавливаются как необязательные, а затем пересекаются с объектом, который выбирает требуемые свойства, но я не уверен, как это сделать. sh нечто подобное с кортежами.

1 Ответ

2 голосов
/ 16 июня 2020

Обратите внимание, что есть возможная загвоздка в определении того, что вы хотите. Необязательные элементы в типах кортежей разрешены только для элементов, каждый последующий элемент также является необязательным. Таким образом, вы можете писать [1, 2?, 3?], но не [1?, 2?, 3]. Это означает, что если у вас есть кортеж вроде [1, 2|undefined, 3, 4|undefined, 5|undefined], вы можете превратить его в [1, 2|undefined, 3, 4?, 5?] или [1, 2?, 3?, 4?, 5?], но не в [1, 2?, 3, 4?, 5?]. Я предполагаю, что первое (где требуется 3) - это то, что вам нужно в дальнейшем.


К сожалению, нет ничего простого. Манипуляции с типами кортежей в TypeScript несколько рудиментарны. Есть открытая проблема, microsoft / TypeScript # 26223 , которая запрашивает часть этого, а мой комментарий здесь обобщает вид произвольных манипуляций с кортежами, необходимых для ответа на этот вопрос. В частности, из этого комментария потребуется что-то вроде TupleLenOptional.

Можно построить реализацию этого из частей, которые дает нам TypeScript, но есть недостатки. Очевидный недостаток в том, что это некрасиво и сложно; мы должны использовать операции prepend-to-tuple и split-tuple-into-first-and-rest. Такая реализация более затратна для компилятора, чем идеальная версия, в которой вы, предположительно, просто используете сопоставленный кортеж.

Если TypeScript поддерживает циклические условные типы (см. microsoft / TypeScript # 26980 для запрос функции) вы бы хотели их использовать. Поскольку это не так, вам нужно либо обмануть компилятор, чтобы разрешить их, что очень не поддерживается ... или вам нужно развернуть круговой тип в избыточный список похожих типов, который работает только с некоторым фиксированным кортежом length.

Вот моя реализация последнего, которая должна работать с кортежами длиной до 10 или около того:

type Cons<H, T extends any[]> = ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never;
type Tail<T extends any[]> = ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;
type CondPartTuple<T extends any[]> = Extract<unknown extends { [K in keyof T]: undefined extends T[K] ? never : unknown }[number] ? T : Partial<T>, any[]>
type UndefinedToOptionalTuple<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT0<Tail<T>>>>
type PT0<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT1<Tail<T>>>>
type PT1<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT2<Tail<T>>>>
type PT2<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT3<Tail<T>>>>
type PT3<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT4<Tail<T>>>>
type PT4<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT5<Tail<T>>>>
type PT5<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT6<Tail<T>>>>
type PT6<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT7<Tail<T>>>>
type PT7<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT8<Tail<T>>>>
type PT8<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PT9<Tail<T>>>>
type PT9<T extends any[]> = CondPartTuple<T['length'] extends 0 ? [] : Cons<T[0], PTX<Tail<T>>>>
type PTX<T extends any[]> = CondPartTuple<T>; // bail out

Основная c идея: сделайте правую складку (например, reduceRight()) типа кортеж. На каждом этапе у вас есть голова кортежа (первый элемент) и хвост (остальные). Приложите голову к хвосту, а затем проверьте, можно ли присвоить undefined каждому элементу результата. Если да, то измените его на Partial. В противном случае оставьте это в покое.

Это дает желаемый эффект:

type Result = UndefinedToOptionalTuple<[1, 2 | undefined, 3, 4 | undefined, 5 | undefined]>
// type Result = [1, 2 | undefined, 3, (4 | undefined)?, (5 | undefined)?]

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


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

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

...