更新:TypeScript 4.0 将具有 variadic tuple types,这将允许更灵活的内置元组操作。 Push<T, V> 将简单地实现为[...T, V]。因此,整个实现变成了以下相对简单的代码:
type Loader<T extends any[]> = {
add<V>(x: V): Loader<[...T, V]>;
run(): T
}
declare const loader: Loader<[]>;
var result = loader.add(1).add("hello").add(true).run(); //[number, string, boolean]
Playground link
对于 v4.0 之前的 TS:
遗憾的是,TypeScript 中没有支持的方式来表示将类型附加到元组末尾的类型操作。我将此操作称为Push<T, V>,其中T 是一个元组,V 是任何值类型。 有一种方法可以在元组的开始上表示预先一个值,我称之为Cons<V, T>。这是因为在 TypeScript 3.0 中,treat tuples as the types of function parameters 引入了一项功能。我们还可以得到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 的自然表示将是这个recursive thing that doesn't work:
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 并按下 @987654345 @ 加到尾部 T... 然后将 H 加回结果中)。
虽然可以诱使编译器允许这种递归类型,it is not recommended。我通常做的是选择一些我想支持修改的最大合理长度的元组(比如 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<[1,2,3],4> 是Array<1 | 2 | 3 | 4>)故意在PushX 处截断。
现在我们可以这样做了:
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]
看起来不错。希望有帮助;祝你好运!
更新
以上内容仅适用于启用--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(n2) 增长)而不是线性(O(n)增长),这不太好。无论如何,它可以通过使用 TS3.1 中引入的mapped tuples 来工作。
这取决于你。
再次祝你好运!