Almaceno valores y funciones tipeadas individuales en una matriz de objetos. Al hacer un bucle en la matriz, se requieren todos los tipos de todas las funciones tipeadas en la matriz para cualquier valor. ¿Cómo puedo escribirlo más específico?
ERROR TS
Argumento de tipo 'booleano | string[]' no se puede asignar a un parámetro de tipo 'string[] & boolean'.
El tipo 'falso' no se puede asignar al tipo 'cadena [] & booleano'.
const arrayToString = (arg: string[]): string => arg.join(", ") const booleanToString = (arg:boolean): string => (arg ? "yes" : "no") const data = [{ value: true, render: booleanToString }, { value: ["a","b"], render: arrayToString } ] data.map(item =>{ item.render(item.value) // ts error })Una forma de evitar esto es declarando el argumento como un tipo de intersección:
render(item.value as boolean & string[]) // no errorEsto le dice al compilador que acepte ambos tipos. Funciona de forma muy similar al comportamiento polimórfico porque el tipo incluye ambas posibilidades.
Esta es una deficiencia de Typescript y una limitación de diseño. Escribe correctamente item.value como boolean | string[] y, a la inversa, escribe correctamente item.render como typeof arrayToString | typeof booleanToString . Sin embargo, para garantizar la seguridad de tipos, Typescript no sabe qué función es, por lo que para garantizar que no se producirá un error en ninguna de las dos, se cruza con los parámetros, por lo tanto, boolean & string[] . La intersección de estas dos primitivas never es . Esto se debe a que los tipos de mecanografiados no tienen discriminación iterable.
Una posible solución es lanzarlo como never .
data.map(({value, render}) =>{ render(value as never); })Mi recomendación (si está configurando datos de esta manera) es crear un tipo para asegurarse de que el procesamiento siempre acepte el valor asociado como parámetros
type ObjectWithRender<T> = { value: T render: (arg: T) => string } const data: [ ObjectWithRender<boolean>, ObjectWithRender<[string, string]> ] = [ { value: true, render: booleanToString }, { value: ["a","b"], render: arrayToString } ]O, alternativamente, cree una función auxiliar para inferir el valor y renderValue... Pero esto tiene algunas desventajas, es decir, sus funciones ahora tendrán que aceptar argumentos de solo lectura. También afectará la compilación al tiempo de ejecución con una función efectivamente inútil.
function validateData<T extends ReadonlyArray<any>>(data: { [K in keyof T]: { value: T[K] render: (arg: T[K]) => string } } ) {return data} // Will throw an error if value isn't the render arg const validatedData = validateData( [ { value: true, render: booleanToString }, { value: ['a', 'b'], render: arrayToString } ] as const )