타입스크립트에서 함수의 타입을 정의하고, 함수를 생성할 때 타입을 할당할 수 없다는 에러를 접하는 경우가 있습니다. 함수의 파라미터는 넓은 타입을 좁은타입에 대입할 수 있고, 반대로 함수의 리턴 타입은 좁은 타입을 넓은 타입에 대입할 수 있습니다.
이러한 현상을 타입스크립트의 공변성(Covariance)
, 반공변성(Contravariance)
의 개념으로 이해할 수 있는데요. 공변성
, 반공변성
단어부터 어렵고 헷갈린 개념이기에 먼저 각 함수의 예시를 통해 간단히 살펴보도록 하겠습니다.
let wide = (param : string | number ):string => {
return param.toString();
}
type narrow = (param : string ) => string;
let wideToNarrow: narrow = wide;
wide
와 narrow
는 함수의 리턴값이 string
으로 동일합니다. wide
의 파라미터에는 string
타입과 number
타입이 들어올 수 있고, narrow
의 파라미터로는 string
만 들어올 수 있습니다. 즉, wide
가 narrow
보다 넓은 타입입니다.
위 예제에서는 넓은 타입의 파라미터를 가진 함수 wide
를 좁은 타입의 파라미터를 가진 narrow
타입에 대입했고, 이때 타입 에러는 발생하지 않습니다.
하지만 반대로 좁은 타입의 파라미터를 가진 함수를 넓은 타입의 파라미터를 가진 함수에 대입하면 어떻게 될까요?
let narrow = (param : string ):string => {
return param.toString();
}
type wide = (param : string | number) => string;
let narrowToWide: wide = narrow;
좁은 타입의 파라미터를 가진 narrow
함수를 넓은 타입의 파라미터를 가진 wide
에 할당하게 되면 number
을 string
에 대입 할 수 없다는 타입 에러가 발생합니다.
Type 'number' is not assignable to type 'string'.
이번엔 리턴 타입의 타입 범위가 다른 경우를 살펴보겠습니다.
let narrow = (param : string ):string => {
return param.toString();
}
type wide = (param : string ) => string | number;
let narrowToWide: wide = narrow;
wide
와 narrow
는 함수의 파라미터 타입이 string
으로 동일합니다. wide
는 string
타입과 number
타입을 리턴하고 narrow
는 string
만 리턴합니다. 즉, wide
가 narrow
보다 넓은 타입을 리턴합니다.
위 예제에서는 좁은 타입을 리턴하는 narrow
를 넓은 타입을 리턴하는 wide
에 대입했고, 이때 타입 에러는 발생하지 않습니다.
let wide = (param : string ):string | number => {
return param.toString();
}
type narrow = (param : string ) => string;
let wideToNarrow: narrow = wide;
하지만 반대로 리턴타입이 좁은 타입에 리턴 타입이 넓은 타입을 대입 하게 되면 number
을 string
에 할당 할 수 없다는 타입 에러가 발생합니다.
Type 'number' is not assignable to type 'string'.
정리를 해보자면, 함수의 파라미터는 좁은 타입에 넓은 타입이 들어올 수 있고 함수의 리턴타입은 넓은 타입에 좁은 타입이 들어올 수 있습니다. 좁은 타입에 넓은 타입을 넣을 수 있는 것을 공변성(Covariance)
, 넓은 타입에 좁은 타입을 넣을 수 있는 성질을 반공변성(Contravariance)
이라고 합니다.
타입스크립트는 기본적으로 공변적으로 동작하나, 앞서 본 것처럼 타입스크립트의 파라미터는 반공변적
으로 동작합니다.
사실 타입스크립트의 파라미터가 반공변적
으로 동작하는 건 TS conifg의 --strictFunctionTypes
옵션이 적용된 strict 모드 기준 입니다. TS conifg의 --strictFunctionTypes
옵션을 on 해야 파라미터의 반공변적
인 성질을 타입스크립트의 규칙으로 삼을 수 있습니다.
만약, 파라미터가 반공변적으로 동작하지 않는다면, string
과 number
를 파라미터로 받아 모두 처리해줘야 하는 함수에 string
타입만 처리할 수 있는 함수를 할당해도 타입 에러가 발생하지 않게 되고, 결국 타입 안정성을 보장하지 못하게 됩니다.
--strictFunctionTypes
가 off인 상태, 즉 strict 모드가 아니라면 함수 파라미터는 공변성
과 반공변성
을 모두 가지게 됩니다. 좁은 타입을 넓을 타입에 할당할 때나 넓은 타입을 좁은 타입에 할당할 때 어떤 경우에도 타입 에러가 발생하지 않게 되는 것이죠. 이렇게 공변적인
성질과 반공변적인
성질을 모두 가진 것을 이변성(Bivariance)
이라고 합니다.
공변성과 반공변성 그리고 이변성의 개념을 공부하면서 변성, 리스코프 치환 원칙 등 추가적으로 이해가 필요한 개념이 많았는데요. 이 부분은 아직 글에 녹여낼만큼 완벽하게 이해하지 못한 관계로, 이번 글은 간단한 함수 예제와 이 예제를 통한 공변성, 반공변성 설명으로 마무리 짓겠습니다. (to be continued..⭐️)