Вариантность описывает, как подтипирование составного типа соотносится с подтипированием его частей — то есть когда Container<Sub> может быть присвоен Container<Super>?
Пусть Dog — подтип Animal.
Вариантность описывает, как подтипирование составного типа соотносится с подтипированием его частей — то есть когда Container<Sub> может быть присвоен Container<Super>?
Пусть Dog — подтип Animal.
let dogs: Dog[] = [];
let animals: Animal[] = dogs; // ✅ Dog[] is assignable to Animal[]
Типы возврата и массивы ковариантны: если Dog ⊆ Animal, то Dog[] ⊆ Animal[]. Функция, возвращающая Dog, может использоваться там, где ожидается функция, возвращающая Animal.
type Handler<T> = (arg: T) => void;
let animalHandler: Handler<Animal> = (a) => {};
let dogHandler: Handler<Dog> = animalHandler; // ✅ (with strictFunctionTypes)
// a handler that accepts ANY Animal can safely handle a Dog
Параметры функции контравариантны: Handler<Animal> может быть присвоен Handler<Dog>, что является обратным соотношением элементов. Это корректно — функция, обрабатывающая всех животных, определённо обработает собак.
// Method parameters in TS are bivariant by default (a known unsound convenience)
interface Comparer<T> { compare(a: T): void; }
TypeScript проверяет типы автономных функций контравариантно только при strictFunctionTypes; параметры методов намеренно бивариантны для удобства использования, что технически небезопасно.
Вариантность объясняет, почему некоторые присвоения разрешены или запрещены — почему Dog[] подходит Animal[], но callback (d: Dog) => void не всегда может использоваться вместо (a: Animal) => void.
Понимание этого помогает вам проектировать универсальные API (например, позиции только для чтения и для записи) и разбираться в запутанных ошибках "not assignable", связанных с функциями и обобщёнными типами.
Библиотека вопросов для IT-собеседований с подробными ответами — от Junior до Senior.
Поддержать