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

时间:2019-03-08 16:11:58

标签: typescript typescript-generics

我想执行以下操作:

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

我想以这种方式构造此理论上的loader对象,而无需手动将其声明为[number, string, boolean]。有没有办法在TypeScript中做到这一点?

1 个答案:

答案 0 :(得分:3)

不幸的是,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;

给定ConsTailPush的自然表示就是这个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>是{{ 1}})。

现在我们可以这样做:

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

对于小的受支持的元组来说,这更简洁,这很好,但是重复的次数是受支持的元组的数目(O(n 2 )增长)的二次方,而不是线性的(O(n))增长),这不太好。无论如何,它都可以使用TS3.1中引入的mapped tuples来工作。

由您决定。

祝你好运!