Necesito ayuda en mi aventura de TypeScript.
Aquí está mi tipo de ejemplo:
type Target = { names: string[] addresses: { location: string }[] };Aquí está mi objeto de ejemplo:
const myTarget: Target = { names: ["Alex", "Nico"], addresses: [ { location: "Miami", }, { location: "New York", }, ], };Y aquí está mi ejemplo de función:
const changeValue = ( key: keyof Target, value: string | Record<string, unknown> ) => { myTarget[key] = [...myTarget[key], value] } changeValue('names', 'Elvis') changeValue('addresses', { location: "Atlanta" }) Tengo un error en myTarget[key] que dice esto:
No se puede asignar el tipo '(cadena | Record<cadena, desconocido>)[]' para escribir 'cadena[] & { ubicación: cadena; }[]'.
No se puede asignar el tipo '(cadena | Record<cadena, desconocido>)[]' para escribir 'cadena[]'.
No se puede asignar el tipo 'cadena | Record<cadena, desconocido>' de tipo 'cadena'.
No se puede asignar el tipo 'Record<cadena, desconocido>' para escribir 'cadena' .ts(2322)
Por lo que entiendo es que, por ejemplo, TypeScript está probando todas las combinaciones, incluida la adición de un objeto a la matriz de names , que no es válida, o la adición de una cadena dentro de la matriz de addresses .
¿Puede alguien ayudarme a entender cuál es el problema y cómo puedo manejarlo? (el proyecto real es mucho más complicado, pero tal vez con este ejemplo lo entienda un poco...)
Muchas gracias.
En general, cuando desea pasar una clave de un tipo de objeto y un valor del tipo de esa clave, puede hacerlo así:
const changeValue = <K extends keyof Target, V extends Target[K]>( key: K, value: V ) => { myTarget[key] = value; } Es decir, le dices al compilador que value no solo es del mismo tipo que algún valor del objeto, sino del mismo tipo que el dado por key .
Sin embargo, aumentaste la dificultad jugando con arreglos. Hasta donde yo sé, no existe un tipo de utilidad para extraer el tipo de una matriz, así que escribí uno:
type ArrayType<T> = T extends Array<infer U>? U : never;Ahora podemos usarlo:
const appendValue = <K extends keyof Target, V extends ArrayType<Target[K]>>( key: K, value: V ) => { myTarget[key] = [...myTarget[key], value] as Target[K]; } Desafortunadamente, como puede ver, el compilador se confunde un poco acerca de la relación entre V[] y Target[K] , por lo que necesita la pista en forma de as .
Ahora las llamadas se compilan limpiamente:
appendValue('names', 'Elvis') appendValue('addresses', { location: "Atlanta" })Por cierto, haces algunas cosas en tu código de ejemplo (aunque quizás no en tu código real) que te recomiendo que no hagas.
Primero, tiene datos globales mutables , myTarget . Hic fons lacrimis , no hagas esto, en ningún idioma, si puedes evitarlo.
Además, mutar datos en general es una mala idea . Considere esto en su lugar:
const appendValue = <K extends keyof Target, V extends ArrayType<Target[K]>>( target: Target, key: K, value: V ): Target => ({ ...target, [key]: [...myTarget[key], value] as Target[K] })¿Por qué tienes un error?
Puede encontrar la respuesta a esta pregunta aquí , aquí , aquí y en mi artículo aquí .
Usando este tipo string | Record<string, unknown> para el argumento de value no es seguro. Es mejor inferir el tipo de argumento key :
const changeValue = <Key extends keyof Target>( key: Key, value: Target[Key][number] ) => { // error myTarget[key] = [...myTarget[key], value] } Tiene un error aquí, porque no hay una correlación (dentro) del cuerpo de la función entre la key y el value . Tal correlación existe solo fuera de la función, es decir, cuando llamas a esta función.
Tenga en cuenta que la key sigue siendo un tipo de unión. Creo que vale la pena usar el enfoque inmutable:
type Target = { names: string[] addresses: { location: string }[] }; const myTarget: Target = { names: ["Alex", "Nico"], addresses: [ { location: "Miami", }, { location: "New York", }, ], }; const withTarget = < Obj extends Record<string, unknown[]> >(target: Obj) => < Key extends keyof Obj >( key: Key, value: Obj[Key][number] ): Obj => ({ ...target, [key]: target[key].concat(value) }) const changeValue = withTarget(myTarget); const result = changeValue('names', 'Elvis')