Варіантність описує, як підтипи узагальненого типу пов'язані з підтипами його параметра. Нехай Dog - підтип Animal.
- Коваріантність (як у «виробника» значень):
Producer<Dog>можна використати там, де очікуєтьсяProducer<Animal>. ПоверненийDog- тежAnimal; - контраваріантність (як у «споживача»): навпаки,
Consumer<Animal>підходить замістьConsumer<Dog>. Функція, що вміє обробити будь-яку тварину, обробить і собаку; - інваріантність - коли тип і читається, і записується: жоден з варіантів не безпечний.
Параметри функцій - контраваріантні (з strictFunctionTypes, що входить у strict):
interface Animal { name: string }
interface Dog extends Animal { bark(): void }
type Handler = (animal: Animal) => void;
const dogHandler = (dog: Dog) => dog.bark();
const h: Handler = dogHandler; // помилка: обробнику передадуть кота, а він викличе bark()
Але методи - біваріантні, і це свідома діра в системі типів:
interface WithMethod { handle(animal: Animal): void } // синтаксис методу
interface WithProperty { handle: (animal: Animal) => void } // властивість-функція
const m: WithMethod = { handle: dogHandler }; // компілюється!
const p: WithProperty = { handle: dogHandler }; // помилка
strictFunctionTypes перевіряє лише типи, оголошені як функції-властивості. Методи лишено біваріантними для сумісності з величезною кількістю наявного коду (наприклад, Array<T>.push(item: T) - метод, і без біваріантності Dog[] не був би сумісний з Animal[]).
Масиви коваріантні - і через це несоундні:
const dogs: Dog[] = [];
const animals: Animal[] = dogs; // дозволено
animals.push({ name: 'Мурка' }); // кіт потрапив у масив собак
dogs[0].bark(); // падіння під час виконання
readonly Animal[] прибирає проблему: в нього не можна записувати.
Анотації варіантності (TS 4.7+) - явно вказати варіантність параметра:
interface Producer<out T> { get(): T }
interface Consumer<in T> { accept: (value: T) => void }
interface State<in out T> { get(): T; set: (v: T) => void }
Вони прискорюють перевірку великих узагальнених типів і перевіряють, що оголошення відповідає заявленій варіантності. Але через біваріантність методів анотація out T поряд з методом set(v: T) помилки не дасть - лише з властивістю-функцією.
Практичні висновки: у власних інтерфейсах для колбеків надавати перевагу синтаксису властивості (onChange: (v: T) => void) - отримаєте строгішу перевірку; для параметрів, які функція лише читає, - readonly масиви.