Push type to the end of the tuple with skipping optional

前端 未结 1 482
没有蜡笔的小新
没有蜡笔的小新 2021-01-23 10:19

I\'ve found out how to push type to the end of the tuple:

type Cons =
    ((head: H, ...tail: T) => void) extends ((...cons         


        
1条回答
  •  北恋
    北恋 (楼主)
    2021-01-23 10:56

    UPDATE: TypeScript 4.0 will feature variadic tuple types, which will allow more flexible built-in tuple manipulation. Push will be simply implemented as [...T, V].


    Pre-TS4.0 answer:

    Ugh, why?! Ahem, I mean, I can maybe do this, but the more type-juggling involved, the less I'd recommend doing this for anything important. There's a library called ts-toolbelt which is close to being "officially supported" by TypeScript (although it doesn't work in the Playground, at least not yet, so I'm not about to make an Stack Overflow answer that requires it) where you can probably build something that works.

    How I'd approach this is to convert tuples with optional elements to unions of tuples without them. Unfortunately, I'm missing a built-in way to take a number type like 6 and get a tuple of that length. So I'm making a hardcoded list of tuples-of-various-lengths that I can map over. You can extend it if you need this to work on longer tuples. Here we go:

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

    That's just the standard Cons<1, [2,3,4]> becomes [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
    

    That's the big list of tuples. So Tup[4] is [0,0,0,0], etc.

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

    That type takes a tuple T and a length N and truncates T to length N. So TruncateTuple<[1,2,3,4], 2> should be [1,2]. It works by getting a tuple of length N from Tup, and maps over it with properties from T.

    type OptTupleToUnion =
        TruncateTuple, T['length']>;
    

    Here's the main event... OptTupleToUnion takes a tuple T and produces a union from it of non-optional tuples. It works by truncating Required (that is, T with optional elements turned into required ones) to length T['length'] which is the union of possible lengths of T. So OptTupleToUnion<[1,2,3?,4?]> should become [1,2] | [1,2,3] | [1,2,3,4].

    Then I'll rename my old Push out of the way to _Push:

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

    and make Push act on OptTupleToUnion instead of T:

    type Push = T extends any ?
        _Push, V> : never;
    

    (with the same T extends any ? ..T.. : never to make sure unions get distributed)

    Let's see if it works:

    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]
    

    Yay, looks good.

    0 讨论(0)
提交回复
热议问题