Вариантность описывает, как совместимость типов «передаётся» через параметры дженериков. Вопрос: если Dog extends Animal, то как соотносятся Box<Dog> и Box<Animal>?
class Animal { breathe() {} }
class Dog extends Animal { bark() {} }
class Cat extends Animal { meow() {} }
// Dog — подтип Animal (Dog extends Animal)**Ковариантный** означает: если Dog extends Animal, то Producer<Dog> совместим с Producer<Animal>.
// Producer<T> — только возвращает T (out-позиция)
type Producer<T> = { produce(): T }
const dogProducer: Producer<Dog> = { produce: () => new Dog() }
const animalProducer: Producer<Animal> = dogProducer // OK! Ковариантно
// Почему безопасно? Если мы ожидаем Animal, а получаем Dog — всё норм.
// Dog является Animal.**Контравариантный** означает: если Dog extends Animal, то Consumer<Animal> совместим с Consumer<Dog>.
// Consumer<T> — только принимает T (in-позиция)
type Consumer<T> = { consume(value: T): void }
const animalConsumer: Consumer<Animal> = { consume: (a) => a.breathe() }
const dogConsumer: Consumer<Dog> = animalConsumer // OK! Контравариантно
// Почему безопасно? Consumer<Animal> принимает любое Animal.
// Dog — это Animal, значит он тоже будет принят.
// Наоборот НЕБЕЗОПАСНО:
// const animalConsumer2: Consumer<Animal> = dogConsumer // Ошибка!
// Если подставить Cat (тоже Animal), но Consumer<Dog> ожидает Dog.bark() — ошибка!В TypeScript методы классов и объектов исторически бивариантны — они принимают и супертипы, и подтипы в позиции параметра:
interface A {
method(x: string): void // бивариантно (для обратной совместимости)
fn: (x: string) => void // контравариантно со strictFunctionTypes
}При включённом strictFunctionTypes: true функции в позиции свойств (не методов) становятся контравариантными.
// out — ковариантный параметр (только возвращается)
type Producer<out T> = {
produce(): T
// consume(value: T): void // Ошибка TS: out позиция, нельзя в параметре
}
// in — контравариантный параметр (только принимается)
type Consumer<in T> = {
consume(value: T): void
// produce(): T // Ошибка TS: in позиция, нельзя в возвращаемом типе
}
// in out — инвариантный (и принимается, и возвращается)
type Transformer<in out T> = {
transform(value: T): T
}// Зная вариантность, понимаем почему работает код:
function handleAnimal(handler: (animal: Animal) => void, dog: Dog) {
handler(dog) // OK — Dog является Animal
}
const logAnimal = (animal: Animal) => animal.breathe()
const logDog = (dog: Dog) => dog.bark()
handleAnimal(logAnimal, new Dog()) // OK — ковариантно по собаке
// handleAnimal(logDog, new Dog()) // Ошибка! handler ожидает Animal, но logDog хочет bark()Демонстрация ковариантности и контравариантности через runtime примеры: безопасные и небезопасные замены типов
// Вариантность — теоретическая концепция TypeScript,
// но мы можем показать её принципы через runtime-примеры.
// Иерархия типов:
// Animal ← Dog ← GoldenRetriever
class Animal {
constructor(name) {
this.name = name
}
breathe() {
return `${this.name} дышит`
}
toString() {
return `Animal(${this.name})`
}
}
class Dog extends Animal {
bark() {
return `${this.name} лает: Гав!`
}
}
class GoldenRetriever extends Dog {
fetch() {
return `${this.name} принёс мяч!`
}
}
class Cat extends Animal {
meow() {
return `${this.name} мяукает`
}
}
// Ковариантность: Producer<Dog> совместим с Producer<Animal>
// Производители возвращают тип — можно идти "вверх по иерархии"
class DogProducer {
produce() { return new Dog('Rex') }
}
class AnimalProducer {
produce() { return new Animal('Generic') }
}
// В TypeScript: const p: Producer<Animal> = new DogProducer() — OK (ковариантно)
function useAnimalProducer(producer) {
const animal = producer.produce()
// Мы ожидаем Animal — Dog тоже Animal, всё безопасно
console.log(animal.breathe())
}
console.log('=== Ковариантность (Producer) ===')
useAnimalProducer(new AnimalProducer()) // OK — Animal
useAnimalProducer(new DogProducer()) // OK — Dog тоже Animal (ковариантно)
// Контравариантность: Consumer<Animal> совместим с Consumer<Dog>
// Потребители принимают тип — можно идти "вниз по иерархии"
const consumeAnimal = (animal) => {
// Работает с любым Animal
console.log(`Обработка животного: ${animal.breathe()}`)
}
const consumeDog = (dog) => {
// Требует Dog — вызывает bark()
console.log(`Обработка собаки: ${dog.bark()}`)
}
console.log('\n=== Контравариантность (Consumer) ===')
function useDogConsumer(consumer) {
const dog = new Dog('Buddy')
// Передаём Dog в consumer
consumer(dog)
}
// Безопасно: consumeAnimal работает с любым Animal (в т.ч. Dog)
useDogConsumer(consumeAnimal) // OK — контравариантно
// НЕБЕЗОПАСНО: consumeDog требует Dog.bark(), но может получить Cat
function useAnimalConsumer(consumer) {
const cat = new Cat('Whiskers')
// Опасность: если consumer ожидает Dog, а получает Cat — bark() не существует!
try {
consumer(cat)
} catch (e) {
console.log(`Ошибка контравариантности: ${e.message}`)
}
}
console.log('\n=== Небезопасная замена (нарушение контравариантности) ===')
useAnimalConsumer(consumeDog) // ОШИБКА: cat.bark не является функцией
// Инвариантность: Transformer<Dog> ≠ Transformer<Animal>
// Если тип и читается, и записывается — замена невозможна
class AnimalTransformer {
transform(animal) {
animal.isTransformed = true
return animal
}
}
class DogTransformer {
transform(dog) {
dog.isTransformed = true
dog.bark() // требует Dog!
return dog
}
}
console.log('\n=== Инвариантность (Transformer) ===')
const at = new AnimalTransformer()
const dog = new Dog('Max')
console.log(at.transform(dog).breathe()) // OK — принимает любой Animal
const dt = new DogTransformer()
try {
const cat = new Cat('Felix')
dt.transform(cat) // Опасно — cat не имеет bark()
} catch (e) {
console.log('Ошибка инвариантности:', e.message)
}
console.log('\n=== Безопасная цепочка (ковариантность функций) ===')
// Функция принимает широкий тип, возвращает узкий — безопасно
const animalToString = (a) => a.breathe() // Animal → string
const dogToString = (d) => d.bark() // Dog → string
// map ковариантен по возвращаемому типу
const animals = [new Dog('Rex'), new GoldenRetriever('Buddy')]
console.log(animals.map(animalToString)) // OK — используем широкий предикатВариантность описывает, как совместимость типов «передаётся» через параметры дженериков. Вопрос: если Dog extends Animal, то как соотносятся Box<Dog> и Box<Animal>?
class Animal { breathe() {} }
class Dog extends Animal { bark() {} }
class Cat extends Animal { meow() {} }
// Dog — подтип Animal (Dog extends Animal)**Ковариантный** означает: если Dog extends Animal, то Producer<Dog> совместим с Producer<Animal>.
// Producer<T> — только возвращает T (out-позиция)
type Producer<T> = { produce(): T }
const dogProducer: Producer<Dog> = { produce: () => new Dog() }
const animalProducer: Producer<Animal> = dogProducer // OK! Ковариантно
// Почему безопасно? Если мы ожидаем Animal, а получаем Dog — всё норм.
// Dog является Animal.**Контравариантный** означает: если Dog extends Animal, то Consumer<Animal> совместим с Consumer<Dog>.
// Consumer<T> — только принимает T (in-позиция)
type Consumer<T> = { consume(value: T): void }
const animalConsumer: Consumer<Animal> = { consume: (a) => a.breathe() }
const dogConsumer: Consumer<Dog> = animalConsumer // OK! Контравариантно
// Почему безопасно? Consumer<Animal> принимает любое Animal.
// Dog — это Animal, значит он тоже будет принят.
// Наоборот НЕБЕЗОПАСНО:
// const animalConsumer2: Consumer<Animal> = dogConsumer // Ошибка!
// Если подставить Cat (тоже Animal), но Consumer<Dog> ожидает Dog.bark() — ошибка!В TypeScript методы классов и объектов исторически бивариантны — они принимают и супертипы, и подтипы в позиции параметра:
interface A {
method(x: string): void // бивариантно (для обратной совместимости)
fn: (x: string) => void // контравариантно со strictFunctionTypes
}При включённом strictFunctionTypes: true функции в позиции свойств (не методов) становятся контравариантными.
// out — ковариантный параметр (только возвращается)
type Producer<out T> = {
produce(): T
// consume(value: T): void // Ошибка TS: out позиция, нельзя в параметре
}
// in — контравариантный параметр (только принимается)
type Consumer<in T> = {
consume(value: T): void
// produce(): T // Ошибка TS: in позиция, нельзя в возвращаемом типе
}
// in out — инвариантный (и принимается, и возвращается)
type Transformer<in out T> = {
transform(value: T): T
}// Зная вариантность, понимаем почему работает код:
function handleAnimal(handler: (animal: Animal) => void, dog: Dog) {
handler(dog) // OK — Dog является Animal
}
const logAnimal = (animal: Animal) => animal.breathe()
const logDog = (dog: Dog) => dog.bark()
handleAnimal(logAnimal, new Dog()) // OK — ковариантно по собаке
// handleAnimal(logDog, new Dog()) // Ошибка! handler ожидает Animal, но logDog хочет bark()Демонстрация ковариантности и контравариантности через runtime примеры: безопасные и небезопасные замены типов
// Вариантность — теоретическая концепция TypeScript,
// но мы можем показать её принципы через runtime-примеры.
// Иерархия типов:
// Animal ← Dog ← GoldenRetriever
class Animal {
constructor(name) {
this.name = name
}
breathe() {
return `${this.name} дышит`
}
toString() {
return `Animal(${this.name})`
}
}
class Dog extends Animal {
bark() {
return `${this.name} лает: Гав!`
}
}
class GoldenRetriever extends Dog {
fetch() {
return `${this.name} принёс мяч!`
}
}
class Cat extends Animal {
meow() {
return `${this.name} мяукает`
}
}
// Ковариантность: Producer<Dog> совместим с Producer<Animal>
// Производители возвращают тип — можно идти "вверх по иерархии"
class DogProducer {
produce() { return new Dog('Rex') }
}
class AnimalProducer {
produce() { return new Animal('Generic') }
}
// В TypeScript: const p: Producer<Animal> = new DogProducer() — OK (ковариантно)
function useAnimalProducer(producer) {
const animal = producer.produce()
// Мы ожидаем Animal — Dog тоже Animal, всё безопасно
console.log(animal.breathe())
}
console.log('=== Ковариантность (Producer) ===')
useAnimalProducer(new AnimalProducer()) // OK — Animal
useAnimalProducer(new DogProducer()) // OK — Dog тоже Animal (ковариантно)
// Контравариантность: Consumer<Animal> совместим с Consumer<Dog>
// Потребители принимают тип — можно идти "вниз по иерархии"
const consumeAnimal = (animal) => {
// Работает с любым Animal
console.log(`Обработка животного: ${animal.breathe()}`)
}
const consumeDog = (dog) => {
// Требует Dog — вызывает bark()
console.log(`Обработка собаки: ${dog.bark()}`)
}
console.log('\n=== Контравариантность (Consumer) ===')
function useDogConsumer(consumer) {
const dog = new Dog('Buddy')
// Передаём Dog в consumer
consumer(dog)
}
// Безопасно: consumeAnimal работает с любым Animal (в т.ч. Dog)
useDogConsumer(consumeAnimal) // OK — контравариантно
// НЕБЕЗОПАСНО: consumeDog требует Dog.bark(), но может получить Cat
function useAnimalConsumer(consumer) {
const cat = new Cat('Whiskers')
// Опасность: если consumer ожидает Dog, а получает Cat — bark() не существует!
try {
consumer(cat)
} catch (e) {
console.log(`Ошибка контравариантности: ${e.message}`)
}
}
console.log('\n=== Небезопасная замена (нарушение контравариантности) ===')
useAnimalConsumer(consumeDog) // ОШИБКА: cat.bark не является функцией
// Инвариантность: Transformer<Dog> ≠ Transformer<Animal>
// Если тип и читается, и записывается — замена невозможна
class AnimalTransformer {
transform(animal) {
animal.isTransformed = true
return animal
}
}
class DogTransformer {
transform(dog) {
dog.isTransformed = true
dog.bark() // требует Dog!
return dog
}
}
console.log('\n=== Инвариантность (Transformer) ===')
const at = new AnimalTransformer()
const dog = new Dog('Max')
console.log(at.transform(dog).breathe()) // OK — принимает любой Animal
const dt = new DogTransformer()
try {
const cat = new Cat('Felix')
dt.transform(cat) // Опасно — cat не имеет bark()
} catch (e) {
console.log('Ошибка инвариантности:', e.message)
}
console.log('\n=== Безопасная цепочка (ковариантность функций) ===')
// Функция принимает широкий тип, возвращает узкий — безопасно
const animalToString = (a) => a.breathe() // Animal → string
const dogToString = (d) => d.bark() // Dog → string
// map ковариантен по возвращаемому типу
const animals = [new Dog('Rex'), new GoldenRetriever('Buddy')]
console.log(animals.map(animalToString)) // OK — используем широкий предикатРеализуй три функции демонстрирующих принципы вариантности. `covariantMap(array, transform)` — применяет transform к каждому элементу массива, возвращает массив результатов (аналог ковариантного Producer). `contravariantFilter(array, predicate)` — фильтрует массив используя predicate (аналог контравариантного Consumer). `invariantUpdate(array, index, transform)` — возвращает новый массив где элемент по index заменён результатом transform(element) (читает и записывает — инвариантно). Ни одна из функций не должна мутировать исходный массив.
covariantMap: return array.map(transform). contravariantFilter: return array.filter(predicate). invariantUpdate: const copy = [...array]; copy[index] = transform(copy[index]); return copy. Или: return array.map((el, i) => i === index ? transform(el) : el).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке