繁体   English   中英

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

[英]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;

鉴于ConsTailPush的自然表示将是这种不起作用的递归事物

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM