TypeScript
Compatibilidad de funciones y variance
Reglas de compatibilidad entre funciones en TypeScript y papel de covariance, contravariance y bivariance al asignar callbacks y métodos.
- Última actualización
- Actualizada
- Nivel
- Profundización
TypeScript
Reglas de compatibilidad entre funciones en TypeScript y papel de covariance, contravariance y bivariance al asignar callbacks y métodos.
Una función es compatible con otra cuando puede utilizarse de forma segura donde se espera la segunda.
type Handler = (value: Product) => void;type Callback = (
product: Product,
index: number,
) => void;
const printName = (product: Product) => {
console.log(product.name);
};
const callback: Callback = printName;Quien acepta un solo argumento puede ignorar los demás que el llamador ofrece.
type Callback = (product: Product) => void;
const needsIndex = (
product: Product,
index: number,
) => {};No puede sustituir a Callback, porque el llamador solo garantiza un argumento.
type CreateEntity = () => Entity;
const createProduct = (): Product => ({
id: "p1",
name: "Keyboard",
price: 100,
});
const createEntity: CreateEntity = createProduct;Product puede ser compatible si contiene la estructura requerida por Entity.
La función que retorna un tipo más específico puede sustituir una que promete uno más general.
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;Un handler que acepta cualquier Animal puede manejar un Dog.
Un handler que solo acepta Dog no puede manejar cualquier Animal.
Con strictFunctionTypes, TypeScript comprueba esta relación de forma más segura para propiedades función y callbacks.
type ListenerContainer = {
handle(value: Animal): void;
};Las firmas de método tienen reglas históricamente más permisivas para compatibilidad con patrones comunes. Una propiedad función:
type ListenerContainer = {
handle: (value: Animal) => void;
};puede recibir comprobación más estricta.
Esto importa en APIs mutables y callbacks de librerías.
Un productor:
type Producer<T> = () => T;puede considerarse covariante en T: producir Dog sirve donde se espera producir Animal.
Un consumidor:
type Consumer<T> = (value: T) => void;necesita aceptar al menos todo lo que el contexto enviará.
Una estructura que consume y produce T puede requerir una relación más estricta.
type Box<T> = {
get(): T;
set(value: T): void;
};Permitir sustituciones ingenuas podría introducir un valor que el otro lado no espera.
TypeScript admite anotaciones in y out en ciertos aliases de tipos de objeto.
type Producer<out T> = () => T;
type Consumer<in T> = (value: T) => void;Sirven para documentar y comprobar relaciones de variance en definiciones avanzadas. No cambian el comportamiento runtime ni deben usarse para forzar una relación incorrecta.
type Handler = () => void;
const handler: Handler = () => {
return 42;
};La función puede retornar un valor, pero el consumidor lo ignora mediante el contrato void.
Esto permite pasar callbacks como array.push, cuyo retorno no interesa a forEach.
Peligroso:
type Callback = (
value: Product,
index?: number,
) => void;Esto significa que quien llama puede omitir index, no que la implementación puede decidir ignorarlo. Si el llamador siempre lo proporciona, decláralo requerido; quien no lo use puede omitirlo de su firma.
function register(
handler: (event: Event) => void,
) {}Pasar (event: MouseEvent) => void no siempre es seguro: register podría invocarlo con un KeyboardEvent.
La API debe ser genérica o específica:
function register<E extends Event>(
handler: (event: E) => void,
) {}Aun así, el runtime debe garantizar qué evento entrega.
type Reader<out T> = {
findById(id: string): T | undefined;
};
type Writer<in T> = {
save(value: T): void;
};Separar lectura y escritura hace más clara la dirección del tipo.
strictFunctionTypes.strictFunctionTypes mejora callbacks y propiedades función.void indica que el consumidor ignora el retorno.¿Por qué (dog: Dog) => void no es un reemplazo seguro para (animal: Animal) => void?
Porque el llamador del segundo contrato puede entregar cualquier Animal, incluido uno que no sea Dog.
this, call signatures y construct signatures modela funciones que dependen de receptor o pueden usarse con new.