我想做以下事情:
var result = loader
.add<number>(1)
.add<string>("hello")
.add<boolean>(true)
.run();
我想以这样的方式构造这个理论上的
loader
对象,使结果的 TYPE 为 [number, string, boolean]
而无需手动声明它。有没有办法在 TypeScript 中做到这一点? 最佳答案
更新: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
并将 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>
)故意切断 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
定义: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 来工作。由你决定。
再次祝你好运!
关于typescript - 通用对象返回类型是方法链的结果,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55066970/