TypeScript를 사용하다 보면 함수 타입 할당에서 예상과 다른 결과를 마주할 때가 있다.
특히 "더 구체적인 타입을 받는 함수를 더 일반적인 타입을 받는 함수에 할당할 수 없다"는 규칙은 처음에는 직관적이지 않게 느껴질 수 있다.
이는 TypeScript의 반공변성(Contravariance)이라는 개념 때문인데,
어려운 용어는 잠시 제쳐두고 음식과 요리사 예시를 통해 이 개념을 이해해보자.
기본 타입 관계부터 살펴보기
먼저 간단한 타입 관계를 정의해보겠다:
type Food = 'pizza' | 'burger' | 'salad'
type Pizza = 'pizza'여기서 Pizza는 Food의 부분집합이다. 따라서 다음과 같은 할당이 가능하다:
const a: Food = 'pizza' // ✅ OK - pizza는 Food에 포함됨
const b: Pizza = 'burger' // ❌ Error - burger는 Pizza 타입이 아님이 부분은 직관적이다. 피자는 음식의 한 종류이니까 음식 변수에 피자를 넣을 수 있고, 피자 변수에는 피자만 들어갈 수 있다.
함수 타입에서는 어떻게 될까?
이제 함수 타입을 정의해보자:
type FoodHandler = (food: Food) => string
type PizzaHandler = (pizza: Pizza) => string
let 만능요리사: FoodHandler = (food) => `${food}를 처리했습니다`
let 피자요리사: PizzaHandler = (pizza) => `${pizza}를 처리했습니다`다음 중 어떤 할당이 가능할까?
// 케이스 1
만능요리사 = 피자요리사 // 가능할까?
// 케이스 2
피자요리사 = 만능요리사 // 가능할까?실제 상황으로 생각해보기
음식점 상황으로 비유해보자.
케이스 1: 만능요리사를 피자요리사로 대체하기
만능요리사 = 피자요리사 // ❌ Type Error뷔페에 있던 요리사가 퇴사를 해서 요리사를 뽑아야 하는데 뷔페의 요리사는 피자, 버거, 샐러드 모든 음식을 처리할 수 있어야 한다. 하지만 피자요리사는 피자만 만들 수 있다.
만약 손님이 버거를 먹고 싶다면? 피자 전문가는 "저는 피자만 만들 수 있어요"라고 할 것이다. 이는 타입 안전성을 깨뜨리는 상황이다.
케이스 2: 피자요리사를 만능요리사로 대체하기
피자요리사 = 만능요리사 // ✅ OK피자 전문점은 피자만 처리하면 된다. 만능 요리사는 피자를 포함한 모든 음식을 처리할 수 있으니까, 당연히 피자도 처리할 수 있다.
피자만 주문이 들어오는 상황에서 만능 요리사를 고용하는 것은 전혀 문제가 없다.
마무리
이 개념은 TypeScript의 타입 안전성을 보장하는 핵심 메커니즘이지만 처음에는 직관적이지 않게 느껴지긴 한다.
하지만 계속 코드를 쓰고 지우다 보면 감이 올 것이라고 생각한다.
'front-end > javascript' 카테고리의 다른 글
| 호이스팅이란? (0) | 2023.01.23 |
|---|---|
| 실행 컨텍스트를 자세히 알아보자 (2) | 2023.01.09 |