我想执行以下操作:
var result = loader
.add<number>(1)
.add<string>("hello")
.add<boolean>(true)
.run();
我想以这种方式构造此理论上的loader
对象,而无需手动将其声明为[number, string, boolean]
。有没有办法在TypeScript中做到这一点?
答案 0 :(得分:3)
不幸的是,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
,只需将V
推到尾巴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
的内容(PushX<[1,2,3],4>
是{{ 1}})。
现在我们可以这样做:
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]
的情况下有效。如果必须在没有该编译器标志的情况下执行操作,则可以改用--strictFunctionTypes
的以下定义:
Push
对于小的受支持的元组来说,这更简洁,这很好,但是重复的次数是受支持的元组的数目(O(n 2 )增长)的二次方,而不是线性的(O(n))增长),这不太好。无论如何,它都可以使用TS3.1中引入的mapped tuples来工作。
由您决定。
祝你好运!