通用对象返回类型是方法链的结果 [英] Generic object return type is result of method chaining

查看:21
本文介绍了通用对象返回类型是方法链的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想执行以下操作:

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

我想构造这个理论上的 loader 对象,使结果的 TYPE 为 [number, string, boolean] 而无需手动声明它像这样.有没有办法在 TypeScript 中做到这一点?

I would like to construct this theoretical loader object in such a way to have the TYPE of result be [number, string, boolean] without needing to manually declare it as such. Is there a way to do this in TypeScript?

推荐答案

更新:TypeScript 4.0 将具有 可变元组类型,这将允许更灵活的内置元组操作.Push 将简单地实现为 [...T, V].因此,整个实现变成了以下相对简单的代码:

UPDATE: TypeScript 4.0 will feature variadic tuple types, which will allow more flexible built-in tuple manipulation. Push<T, V> will be simply implemented as [...T, V]. Therefore the entire implementation turns into the following relatively straightforward bit of code:

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:

FOR TS before v4.0:

不幸的是,TypeScript 中没有支持的方式来表示将类型附加到元组末尾的类型操作.我将此操作称为 Push,其中 T 是元组,V 是任何值类型.有一种方法可以将前置值表示到元组的开头,我将其称为Cons;.这是因为在 TypeScript 3.0 中,向 将元组视为函数参数的类型.我们还可以得到 Tail,它从元组中取出第一个元素(头部)并返回其余元素:

There is unfortunately no supported way in TypeScript to represent the type operation of appending a type onto the end of a tuple. I'll call this operation Push<T, V> where T is a tuple and V is any value type. There is a way to represent prepending a value onto the beginning of a tuple, which I'll call Cons<V, T>. That's because in TypeScript 3.0, a feature was introduced to treat tuples as the types of function parameters. We can also get Tail<T>, which pulls the first element (the head) off a tuple and returns the rest:

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;

给定 ConsTailPush 的自然表示将是这个 不起作用的递归事物:

Given Cons and Tail, the natural representation of Push would be this 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 并将 V 推到尾部 T...然后将 H 重新添加到结果中).

The idea there is that Push<[], V> should just be [V] (appending to an empty tuple is easy), and Push<[H, ...T], V> is Cons<H, Push<T, V>> (you hold onto the first element H and just push V onto the tail T... then prepend H back onto the result).

虽然可以欺骗编译器允许这种递归类型,它不是推荐.我通常做的是选择一些我想要支持修改的最大合理长度的元组(比如 9 或 10),然后展开循环定义:

While possible to trick the compiler into allowing such recursive types, it is not recommended. What I usually do instead is pick some maximum reasonable length of tuple I want to support modifying (say 9 or 10) and then unroll the circular definition:

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>).

Each line except PushX looks just like the recursive definition, and we intentionally cut things off at PushX by giving up and just forgetting about the order of elements (PushX<[1,2,3],4> is 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一个类型(实现由你决定):


Armed with Push, let's give a type to loader (leaving the implementation up to you):

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]

看起来不错.希望有所帮助;祝你好运!

Looks good. Hope that helps; good luck!

以上仅适用于 --strictFunctionTypes 启用.如果您必须不使用该编译器标志,则可以使用以下 Push 定义:

The above only works with --strictFunctionTypes enabled. If you must do without that compiler flag, you could use the following definition of Push instead:

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 中引入的映射元组.

It's more terse for small supported tuple sizes, which is nice, but the repetition is quadratic in the number of supported tuples (O(n2) growth) instead of linear (O(n) growth), which is less nice. Anyway it works by using mapped tuples which were introduced in TS3.1.

这取决于你.

再次祝你好运!

这篇关于通用对象返回类型是方法链的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆