[英]Generic object return type is result of method chaining
我想做以下事情:
var result = loader
.add<number>(1)
.add<string>("hello")
.add<boolean>(true)
.run();
我想以这样的方式构造这个理论loader
对象,使结果的 TYPE 为[number, string, boolean]
而无需手动声明它。 有没有办法在 TypeScript 中做到这一点?
更新:TypeScript 4.0 将具有可变元组类型,这将允许更灵活的内置元组操作。 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]
对于 v4.0 之前的 TS:
不幸的是,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>
是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(n 2 ) 增长)而不是线性(O(n) 增长),这不太好。 无论如何,它通过使用 TS3.1 中引入的映射元组来工作。
由你决定。
再次祝你好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.