К сожалению, в TypeScript не поддерживается способ представления операции с типом добавления типа в конец кортежа. Я назову эту операцию Push<T, V>
, где T
- это кортеж, а V
- это любой тип значения. - это способ представления с добавлением значения к началу кортежа, который я назову Cons<V, T>
. Это связано с тем, что в TypeScript 3.0 была добавлена возможность рассматривать кортежи как типы параметров функции . Мы также можем получить Tail<T>
, который вытаскивает первый элемент (голову) из кортежа и возвращает остаток:
type Cons<H, T extends any[]> =
((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never;
type Tail<T extends any[]> =
((...x: T) => void) extends ((h: infer A, ...t: infer R) => void) ? R : never;
Учитывая Cons
и Tail
, естественным представлением Push
будет эта рекурсивная вещь, которая не работает :
type BadPush<T extends any[], V> =
T['length'] extends 0 ? [V] : Cons<T[0], BadPush<Tail<T>, V>>; // error, circular
Идея заключается в том, что Push<[], V>
должно быть просто [V]
(добавить в пустой кортеж легко), а Push<[H, ...T], V>
равно Cons<H, Push<T, V>>
(вы держитесь за первый элемент H
и просто нажимаете V
на хвост T
... затем добавьте H
обратно на результат).
Хотя компилятор и позволяет использовать такие рекурсивные типы, не рекомендуется . Вместо этого я обычно выбираю максимально допустимую длину кортежа, который я хочу поддержать модификацией (скажем, 9 или 10), а затем развертываю круговое определение:
type Push<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push1<Tail<T>, V>>
type Push1<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push2<Tail<T>, V>>
type Push2<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push3<Tail<T>, V>>
type Push3<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push4<Tail<T>, V>>
type Push4<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push5<Tail<T>, V>>
type Push5<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push6<Tail<T>, V>>
type Push6<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push7<Tail<T>, V>>
type Push7<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push8<Tail<T>, V>>
type Push8<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push9<Tail<T>, V>>
type Push9<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], PushX<Tail<T>, V>>
type PushX<T extends any[], V> = Array<T[number] | V>; // give up
Каждая строка, кроме PushX
, выглядит точно так же, как рекурсивное определение, и мы намеренно обрезаем все на PushX
, отказываясь и просто забывая о порядке элементов (PushX<[1,2,3],4>
is Array<1 | 2 | 3 | 4>
).
Теперь мы можем сделать это:
type Test = Push<[1, 2, 3, 4, 5, 6, 7, 8], 9> // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Вооружившись Push
, давайте присвоим типу loader
(оставляя реализацию до вас):
type Loader<T extends any[]> = {
add<V>(x: V): Loader<Push<T, V>>;
run(): T
}
declare const loader: Loader<[]>;
А давайте попробуем:
var result = loader.add(1).add("hello").add(true).run(); //[number, string, boolean]
выглядит хорошо. Надеюсь, это поможет; удачи!
UPDATE
Вышеуказанное работает только с включенным --strictFunctionTypes
. Если вам нужно обойтись без этого флага компилятора, вы можете использовать следующее определение Push
:
type PushTuple = [[0], [0, 0], [0, 0, 0],
[0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
type Push<
T extends any[],
V,
L = PushTuple[T['length']],
P = { [K in keyof L]: K extends keyof T ? T[K] : V }
> = P extends any[] ? P : never;
Это более кратко для небольших поддерживаемых размеров кортежей, что хорошо, но повторение квадратично по числу поддерживаемых кортежей (O (n 2 ) рост) вместо линейного (O (n) роста ), что менее приятно. В любом случае это работает с использованием сопоставленных кортежей , которые были представлены в TS3.1.
Вам решать.
Удачи снова!