Búsqueda de sitios web

Cómo extender una tupla genérica en TypeScript


Estoy intentando crear una función que decore objetos en un arreglo y los devuelva. El tipo devuelto para cada elemento debe mantener los literales de los genéricos. ¿Hay alguna manera de hacer esto?

type Identified<Id extends string = string> = { id: Id };
type Extended<Id extends string> = Identified<Id> & { test: "test" };

let one: Identified<"one"> = {id:"one"};
let two: Identified<"two"> = {id:"two"};
let three: Identified<"three"> = {id:"three"};

function extendList<A extends Identified>(arr: A[]) {
  return arr.map((item) => ({ ...item, test: "test" }));
}

let extendedList = extendList([one,two,three]); // literal is lost
let oneExtended = extendedList[0]; // this should be {id:"one", test:"test"} (not {id:string...})

Enlace del parque

Respuesta #1

Un enfoque es

function extendList<T extends string[]>(
  arr: [...{ [I in keyof T]: Identified<T[I]> }]
) {
  return arr.map((item) => ({ ...item, test: "test" })) as
    { [I in keyof T]: Extended<T[I]> };
}

La idea es hacer que extendList() sea genérico en T, la tupla de los tipos literales de cadena de las propiedades id de los elementos del parámetro arr. Así que si llamas a extendList([one, two, three]), T debería ser ["one", "two", "three"].

Entonces, el tipo de la entrada arr es una tupla mapeada que convierte cada elemento T[I] (el elemento I de la tupla T) en Identified<T[I]>, mientras que la salida es una tupla mapeada que convierte cada elemento T[I] en Extended<T[I]>.

Tenga en cuenta que el tipo de arr no es solo el tipo mapeado { [I in keyof T]: Identified<T[I]> }, sino que ha sido envuelto en una tupla variádica de la forma [...{ [I in keyof T]: Identified<T[I]> }]; esto es solo para darle al compilador una pista de que debería tender a inferir tipos de tupla en lugar de arreglos desordenados. De lo contrario, extendList([one, two, three]) podría hacer que T se infiera como ("one" | "two" | "three")[], que no es lo que desea.

También tenga en cuenta que necesitaba usar una afirmación de tipo para decirle al compilador que arr.map(item => ({...item, test: "test"})) es del tipo de salida deseado. El compilador no puede inferir eso automáticamente, ni puede verificarlo. Es, esencialmente, más allá de las habilidades del compilador poder "ver" que arr.map(...) hace lo que usted dice que hace. Vea esta respuesta a una pregunta similar para obtener más información sobre esta limitación.

De todos modos, asegurémonos de que se comporte como quieres:

let one: Identified<"one"> = { id: "one" };
let two: Identified<"two"> = { id: "two" };
let three: Identified<"three"> = { id: "three" };
let extendedList = extendList([one, two, three]);
// let extendedList: [Extended<"one">, Extended<"two">, Extended<"three">]
let oneExtended = extendedList[0]; 
// let oneExtended: Extended<"one">

¡Se ve bien!

Enlace de Playground al código

Respuesta #2

Prueba esto

type Identified<Id extends string = string> = { id: Id };
type Extended<Id extends string> = Identified<Id> & { test: "test" };

const one: Identified<"one"> = {id:"one"};
const two: Identified<"two"> = {id:"two"};
const three: Identified<"three"> = {id:"three"};

function extendList<T extends string>(arr: Identified<T>[]): Extended<T>[] {
  return arr.map((item) => ({ ...item, test: "test" }));
}

const extendedList = extendList([one,two,three]);
const oneExtended = extendedList[0];

Artículos relacionados