将类型推到元组的末尾,并跳过可选

时间:2019-10-25 14:44:31

标签: typescript tuples

我已经found out如何将类型推到元组的末尾:

type Cons<H, T extends readonly any[]> =
    ((head: H, ...tail: T) => void) extends ((...cons: infer R) => void) ? R : never;

type Push<T extends readonly any[], V>
    = T extends any ? Cons<void, T> extends infer U ?
    { [K in keyof U]: K extends keyof T ? T[K] : V } : never : never;

通常可以正常工作:

type A = Push<[1, 2, 3], 4>;  // [1, 2, 3, 4]
type B = Push<[1, 2, 3?], 4>; // [1, 2, 3 | undefined, 4?]

但是我实际上希望如果某些参数是可选的,它不包含未定义的内容:

A = [1, 2, 3, 4]
B = [1, 2, 3, 4] | [1, 2, 4] // How can I get this?
B = [1, 2, 3 | undefined, 4] | [1, 2, 4] // or this?

Demo


实际上我正在尝试做this

function doSmth<F extends (...args: any) => number>(f: F, ...args: Push<Parameters<F>, string>) {
  var x: string = args.pop();
  return f(...args) + x;
}

function f1(x: number, y: number) {
  return x + y;
}

function f2(x: number, y?: number) {
  return x + (y || 0);
}

doSmth(f1, 1, 2, "A");
doSmth(f2, 1, 2, "B");
doSmth(f2, 1, "C"); // Problem here: should compile, but it doen't
doSmth(f2, 1, 2);   // And here: should not compile as last is not a string, but it does
doSmth(f2, 1, undefined, "D"); // Possible, but actually I do not care

1 个答案:

答案 0 :(得分:1)

U,为什么?!哎呀,我的意思是,我也许可以做到这一点,但是涉及类型越多,就重要的事情,我建议这样做就越少。有一个名为ts-toolbelt的库,它接近于被TypeScript "officially supported"使用(尽管它在the Playground中不起作用,至少在not yet中不起作用,所以我不会做出一个需要它的Stack Overflow答案),在这里您可以构建一些可行的东西。

我的处理方式是将具有可选元素的元组转换为没有它们的元组的并集。不幸的是,我缺少一种内置的方法来获取像6这样的数字类型并获得该长度的元组。因此,我制作了一个硬编码的可变长度元组列表,可以在上面进行映射。如果需要在更长的元组上使用它,则可以扩展它。我们开始:

type Cons<H, T extends readonly any[]> =
    ((head: H, ...tail: T) => void) extends ((...cons: infer R) => void) ? R : never;

那只是标准Cons<1, [2,3,4]>变成[1,2,3,4]

type Tup = [[], [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],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]; // make as long as you need

那是元组的大清单。因此Tup[4][0,0,0,0],依此类推。

type TruncateTuple<T extends readonly any[], N extends number> = Extract<
    Tup[N] extends infer R ? { [K in keyof R]: K extends keyof T ? T[K] : never }
    : never, readonly any[]>;

该类型采用元组T和长度N,并将T截短为长度N。因此TruncateTuple<[1,2,3,4], 2>应该是[1,2]。通过从N获取长度为Tup的元组来工作,并使用T中的属性在其上进行映射。

type OptTupleToUnion<T extends readonly any[]> =
    TruncateTuple<Required<T>, T['length']>;

这是主要事件... OptTupleToUnion接受一个元组T,并从中产生一个非可选元组的并集。它的工作方式是将Required<T>(即T的可选元素变成必需的元素)截短到长度T['length'],这是T可能长度的并集。因此,OptTupleToUnion<[1,2,3?,4?]>应该变成[1,2] | [1,2,3] | [1,2,3,4]

然后,我将旧的Push重命名为_Push

type _Push<T extends readonly any[], V>
    = T extends any ? Cons<void, T> extends infer U ?
    { [K in keyof U]: K extends keyof T ? T[K] : V } : never : never;

并使Push<T, V>而不是OptTupleToUnion<T>作用于T

type Push<T extends readonly any[], V> = T extends any ?
    _Push<OptTupleToUnion<T>, V> : never;

(使用相同的T extends any ? ..T.. : never来确保联合分发)

让我们看看它是否有效:

type A = Push<[1, 2, 3], 4>;  // [1, 2, 3, 4]
type B = Push<[1, 2, 3?], 4>; // [1, 2, 3, 4] | [1, 2, 4]

是的,看起来不错。 ?如果您开始在这里要求更多功能,我可能不得不放弃...也许其他人的耐力更高!

好的,希望能有所帮助;祝好运!

Link to code