Pesquisa de site

typescript - Como inferir o primeiro elemento de uma tupla como um tipo de tupla


Não consigo inferir corretamente o primeiro tipo de uma tupla de comprimento arbitrário como um tipo de tupla, para manter seu rótulo:

type First1<T extends any[]> = T extends [infer FIRST, ...any[]] ? FIRST : never

type test1 = First1<[a: number, b: boolean, c: string]> // number


type First2<T extends any[]> = T extends [infer FIRST, ...any[]] ? [FIRST] : never

type test2 = First2<[a: number, b: boolean, c: string]> // [number] <-- obv no label


type First3<T extends any[]> = T extends [...infer FIRST, any, ...any[]] ? FIRST : never

type test3 = First3<[a: number, b: boolean, c: string]> // unknown[] <-- I tried ahah

Alguma ideia? Parquinho

Resultado esperado: [a: number]

Resposta #1

A seguinte abordagem funciona para o seu código de exemplo:

type First<T extends any[]> = T extends [any, ...infer R] ?
    T extends [...infer F, ...R] ? F : never : never

type Test = First<[a: number, b: boolean, c: string]>
// type Test = [a: number]

A ideia é pegar o tipo de tupla T e inferir dele a tupla R correspondente a tudo após o primeiro elemento. Uma vez que R está definido, podemos pedir ao compilador para dividir T em duas tuplas, sendo que a última delas é R. Isso retorna uma tupla contendo apenas o primeiro elemento, com o rótulo intacto.


É claro que existem casos extremos que não funcionarão, como elementos opcionais de tupla e elementos rest em tipos de tupla, incluindo elementos rest iniciais e do meio:

type X = First<[a?: number, b?: boolean, c?: string]>; // never
type Y = First<[...a: number[], b: boolean, c: string]> // never
type Z = First<[a: number, ...b: boolean[], c: string]> // unknown[]

Eu não sei se há uma boa maneira de fazer isso que não tenha casos extremos. De acordo com comentários no GitHub como este, preservar os rótulos de tupla através de transformações de tipo não é garantido que funcione em todos os casos, então uma solução parcialmente funcional pode ser o melhor que se consegue.

Link do playground para o código

Resposta #2

Minha solução atual:

type Init<T extends any[]> = T extends [...infer INIT, any] ? INIT : never;

type FirstAsTuple<T extends any[]> = T extends [any]
  ? T extends [...infer F]
    ? F
    : never
  : FirstAsTuple<Init<T>>;

Parque infantil

Esperando se alguém souber de um truque não recursivo.

Artigos relacionados