gpt4 book ai didi

typescript - 通用对象返回类型是方法链接的结果

转载 作者:搜寻专家 更新时间:2023-10-30 21:07:49 24 4
gpt4 key购买 nike

我想做以下事情:

var result = loader
.add<number>(1)
.add<string>("hello")
.add<boolean>(true)
.run();

我想以这样一种方式构造这个理论上的 loader 对象,使结果的类型为 [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;

给定ConsTail , 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)增长),这不太好。无论如何,它可以通过使用 mapped tuples 来工作这是在 TS3.1 中引入的。

这取决于你。

再次祝你好运!

关于typescript - 通用对象返回类型是方法链接的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55066970/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com