typescript - ¿Cómo puedo obtener los tipos comunes en el arreglo?
Tengo la estructura
interface T1<T extends string> {
path: T,
handler: (value: T) => void
};
si uso una función con la misma interfaz no tengo ningún problema con los tipos, todo es correcto
declare function foo1<T extends string>({ path, handler }: T1<T>): void
const handlerWithoutError = (v: 'some_path') => { }
foo1({ path: 'some_path', handler: handlerWithoutError })
const handlerWithError = (v: 'wrong_path') => { }
foo1({ path: 'other_path', handler: handlerWithError })
pero si intento usar un arreglo en lugar de una interfaz obvia, el tipo funciona de manera incorrecta
declare function boo1<T extends string>(arr: T1<T>[]): void
boo1([
{ path: 'some_path', handler: handlerWithoutError },
{ path: 'other_path', handler: handlerWithError },
])
Necesito que el parámetro de la función "handler" coincida con la "ruta" para cada elemento del arreglo por separado. Para el segundo elemento debería ser un error mientras que el primero no lo es. ¿Cómo solucionar ese problema y obligar a TS a mostrar los consejos correctos? Playground
Respuesta #1
Sugiero que utilices la siguiente firma de llamada:
declare function boo1<T extends string[]>(
arr: [...{ [I in keyof T]: T1<T[I]> }]
): void
La función es genérica en el parámetro de tipo T correspondiente a la tupla de argumentos de tipo en foo1(). Luego, arr es del tipo de tupla mapeada {[I in keyof T]: T1<T[I]> } que transforma T en una versión donde cada elemento está envuelto con T1<>. Bueno, principalmente de ese tipo; está envuelto en el tipo de tupla variádica [...+] para darle al compilador una pista de que te gustaría inferir el tipo de arr como una tupla y no como un arreglo desordenado de longitud arbitraria.
Veamos qué pasa cuando lo llamas:
declare const x: T1<"a">;
declare const y: T1<"b">;
declare const z: T1<"c">;
boo1([x, y, z]); // okay
// function boo1<["a", "b", "c"]>(arr: [T1<"a">, T1<"b">, T1<"c">]): void
El argumento arr es [x, y, z]. Esto se infiere como del tipo de tupla [T1<"a">, T1<"b">, T1<"c">] (debido a la tupla variádica [...+] en la firma de llamada; compáralo con lo que sucede si no la usas). Entonces, el compilador puede inferir T a partir del tipo mapeado { [I in keyof T]: T1<T[I]> } como ["a", "b", "c"]. Y dada esa inferencia, la llamada verifica el tipo correctamente.
Veamos qué pasa cuando lo llamas por error:
boo1([
{ path: 'some_path', handler: handlerWithoutError }, // okay
{ path: 'other_path', handler: handlerWithError }, // error!
// -----------------> ~~~~~~~
// Type '"other_path"' is not assignable to type '"wrong_path"'.
])
// function boo1<["some_path", "other_path"]>(
// arr: [T1<"some_path">, T1<"other_path">]): void
Aquí el compilador infiere que T es ["some_path", "other_path"] a partir de las propiedades path. Esto significa que arr debería ser del tipo [T1<"some_path">, T1<"other_path">]. El primer elemento de arr es del tipo T1<"some_path">, pero el segundo elemento no es del tipo T1<"other_path">, y por lo tanto obtienes el error deseado.
Enlace de Playground al código
