Usando uniones de TypeScript en métodos que lanzan errores
Soy relativamente nuevo en TypeScript y tengo lo que creo que es un error extraño.
Tengo múltiples objetos que usan enviar datos al servidor, y luego devolver datos nuevos del mismo objeto desde el servidor. Los diferentes objetos usan el mismo método para enviar los datos, así que he usado un union para especificar que el método puede tomar cualquiera de los dos tipos, pero estoy recibiendo un error.
He hecho una versión simple del código para replicar el problema, lo cual puedo hacer.
type A = {
aField: string;
aaField: string;
}
type B = {
bField: string;
bbField: number
}
type Student = A | B;
type Func = (student: Student) => Student;
const test = {
aField: 'Test',
aaField: 'Test Again'
}
const funcA = (a: A): A => {
return a;
}
const funcB = (b: B): B => {
return b;
}
const func = (student: Student): Student => {
return student;
}
const component = (func: Func) => {
return func;
}
component(func);
component(funcA);
El error que estoy recibiendo es el siguiente
El argumento de tipo '(a: A) => A' no se puede asignar al parámetro de tipo 'Func'.
Los tipos de los parámetros 'a' y 'student' son incompatibles.
El tipo 'Student' no se puede asignar al tipo 'A'.
Al tipo 'B' le faltan las siguientes propiedades del tipo 'A': aField, aaField
¿Hay alguna manera de evitar esto sin tener que convertir el tipo de cada una de las funciones?
Respuesta #1
Un tipo unión es más restrictivo de lo que podrías pensar, cuando se usa para tipar un parámetro callback (como el parámetro func de tu función component).
Cuando tenemos:
type Student = A | B;
type Func = (student: Student) => Student;
...estamos diciendo que Func debe aceptar un argumento del tipo Student, que puede ser (a la voluntad del llamador) ya sea A o B.
Eso es lo que hace tu función func, pero no funcA o funcB, lo cual ya se puede ver si intentamos escribirlas como Func:
// Error: Type '(a: A) => A' is not assignable to type 'Func'.
const funcA: Func = (a: A): A => {
// ~~~~~
return a;
}
// Error: Type '(b: B) => B' is not assignable to type 'Func'.
const funcB: Func = (b: B): B => {
// ~~~~~
return b;
}
// Okay
const func: Func = (student: Student): Student => {
return student;
}
Enlace del parque
funcA y funcB aceptan A y B respectivamente, pero no una unión A | B (funcA no acepta B, y viceversa), por lo tanto no coinciden con Func.
Tengo múltiples objetos que usan enviar datos al servidor, y luego devuelven datos nuevos del mismo objeto desde el servidor.
Por tu descripción, más bien necesitas un parámetro de tipo genérico, que también proporcione el mismo tipo al retorno del callback:
const component2b = <T extends A | B>(func: (student: T) => T) => {
return func;
}
component2b(funcA); // Okay
component2b(funcB); // Okay
Con este tipo genérico T extends A | B, decimos que la función de retorno func debe aceptar un argumento de un tipo T que sea asignable a A | B, por lo que A o B está bien (y no necesariamente ambos simultáneamente).
Además, también especificamos que T es el tipo de retorno, por lo que si la función de devolución de llamada pasada toma A, devuelve también el tipo A (y lo mismo para B), coincidiendo con tu descripción.
Enlace del parque
Respuesta #2
Puedes usar un tipo condicional distributivo para distribuir la unión Student sobre las firmas de función que coincidan:
type Func<S = Student> = S extends Student ? (student: S) => S : never;
component(func); // OK
component(funcA); // OK
component(funcB); // OK
Aquí tienes un enlace de playground con un fragmento completo.
