타입스크립트에서 악명이 높은 공변성과 반공변성에 대해 알아보자.
먼저, 이 포스트는 tsconfig의 strictFunctionTypes 옵션이 true 인 경우를 기준으로 말함을 알린다.
{
"compilerOptions": {
"strictFunctionTypes": true;
}
}
공변성은 한 타입이 다른 타입의 서브타입(subtype)일 때, 그 타입을 사용하는 다른 모든 맥락에서도 서브타입 관계가 유지되는 성질을 말한다.
예를 들어, 배열 타입은 공변적인데, Animal
이 Dog
의 슈퍼타입이라면, Animal[]
은 Dog[]
의 슈퍼타입이다.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
let animals: Animal[] = [{ name: '여름이' }];
let dogs: Dog[] = [{ name: '여름이', breed: '말티즈' }];
animals = dogs; // 정상: Dog[]는 Animal[]의 서브타입
dogs = animals; // 에러: 넓은 타입을 좁은 타입에 대입 X
즉, Dog ⊂ Animal 이므로, Dog[] ⊂ Animal[] 의 관계 또한 성립하는 것을 공변성이라고 한다.
함수의 반환값의 타입 역시 공변적이다.
type giveDog = (a: string) => Dog;
declare const giveMaltese: (a: string) => Maltese;
declare const giveAnimal: (a: string) => Animal;
const dog: giveDog = giveMaltese; // 정상: Maltese ⊂ Dog 이므로 giveMaltese ⊂ giveDog
const dog: giveDog = giveAnimal; // 넓은 타입을 좁은 타입에 대입 X
반공변성은 한 타입이 다른 타입의 서브타입일 때, 그 타입을 매개변수로 사용하는 모든 맥락에서 역으로 서브타입 관계가 유지되는 성질을 말한다.
예를 들어, 함수 매개변수 타입은 반공변적이다.
type takeDog = (a: Dog) => string;
declare const takeMaltese: (a: Maltese) => string;
declare const takeAnimal: (a: Animal) => string;
const dog: takeDog = takeMaltese; // 에러: 함수의 매개변수는 반공변성을 따라 좁은 타입을 넓은 타입에 대입 X
const dog: takeDog = takeAnimal; // 정상: Dog ⊂ Animal 이지만 takeAnimal ⊂ takeDog
반공변성은 타입 시스템에서 매개변수의 타입을 설정할 때 매우 중요하다. 함수의 매개변수 타입이 서브타입으로 대체될 경우, 호출 시점에 잘못된 타입이 전달될 수 있다. 예를 들어, takeDog 타입이 takeMaltese 타입으로 대체되면, Dog 타입의 다른 서브타입 (예: Bulldog)이 전달될 때 문제가 발생할 수 있다. 반대로, 더 넓은 타입인 Animal 타입을 사용하는 것은 안전하다.