Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що таке коваріантність і контраваріантність у TypeScript і чому методи поводяться інакше, ніж функції-властивості?

Варіантність описує, як підтипи узагальненого типу пов'язані з підтипами його параметра. Нехай 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 масиви.

Докладніше в документації: Анотації варіантності

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання