차별된 유니온 타입
TypeScript는 JavaScript가 어떻게 동작하는지 잘 이해하며, 마치 프로그래머가 머리로 프로그램을 추적하듯이 코드로부터 타입을 정제해 낼 수 있습니다.
예를 들어 응용 프로그램의 커스텀 이벤트 시스템을 만든다고 가정합니다.
먼저 몇 가지 이벤트 타입과 이벤트들을 처리할 함수를 정의합니다.
KeyBoardEvent는 키보드 이벤트를, MouseControlEvent는 마우스 이벤트를 가리킵니다.
type KeyBoardEvent = {value: string}
type MouseControlEvent = {value: [number, number]}
type FirstEvent = KeyBoardEvent | MouseControlEvent
function handle(event: FirstEvent){
if(typeof event.value === 'string'){
event.value // string
// 블라 블라
return
}
event.value // [number, number]
}
if 블록 내부에서 TypeScript는 (typeof로 확인했으므로) event.value가 문자열임을 알고 있습니다.
따라서 if 블록 이후의 event.value는 [number, number] 튜플이어야 합니다.
조금 더 복잡해지면 무슨 일이 일어날까요?
이벤트 타입에 정보를 더 추가하면서 타입을 정제할 때 TypeScript가 어떻게 처리되는지 확인해 보겠습니다.
type KeyBoardEvent = {value: string, target: HTMLInputElement}
type MouseControlEvent = {value: [number, number], target: HTMLElement}
type FirstEvent = KeyBoardEvent | MouseControlEvent
function handle(event: FirstEvent){
if(typeof event.value === 'string'){
event.value // (property) value: string
event.target // (property) target: HTMLInputElement | HTMLElement
// 블라 블라
return
}
event.value // (property) value: [number, number]
event.target // (property) target: HTMLInputElement | HTMLElement
}
event.value는 잘 정제되었지만 event.target에는 적용되지 않았습니다.
이유가 뭘까요?
handle이 FirstEvent 타입의 매개변수를 받는다는 것은 KeyBoardEvent나 MouseControlEvent만 전달할 수 있다는 의미가 아닙니다.
사실 KeyBoardEvent | MouseControlEvent 타입의 인수를 전달할 수도 있습니다.
유니온의 멤버가 서로 중복될 수 있으므로 TypeScript는 유니온의 어떤 타입에 해당하는지를 조금 더 안정적으로 파악할 수 있어야 합니다.
리터럴 타입을 이용해 유니온 타입이 만들어낼 수 있는 각각의 경우를 태그(tag)하는 방식으로 이 문제를 해결할 수 있습니다.
아래는 좋은 태그의 조건입니다.
- 유니온 타입의 각 경우와 같은 위치에 있습니다. 객체 타입의 유니온에선 같은 객체 필드를 의미하고, 튜플 타입의 유니온이라면 같은 인덱스를 의미합니다. 보통 태그 된 유니온은 객체 타입을 사용합니다.
- 리터럴 타입입니다. 다양한 리터럴 타입을 혼합하고 매치할 수 있지만 한 가지 타입만 사용하는 것이 바람직합니다. 보통은 문자열 리터럴 타입을 사용합니다.
- 제네릭이 아닙니다. 태그는 제네릭 타입 인수를 받지 않아야 합니다.
- 상호 배타적입니다.(ex. 유니온 타입 내에서 고유함)
이를 생각하면서 이벤트 타입을 다시 바꿔보겠습니다.
type KeyBoardEvent = {type: 'KeyBoard', value: string, target: HTMLInputElement}
type MouseControlEvent = {type: 'MouseControl', value: [number, number], target: HTMLElement}
type FirstEvent = KeyBoardEvent | MouseControlEvent
function handle(event: FirstEvent){
if(event.type === 'KeyBoard'){
event.value // (property) value: string
event.target // (property) target: HTMLInputElement
// 블라 블라
return
}
event.value // (property) value: [number, number]
event.target // (property) target: HTMLElement
}
event를 태그 된 필드(event.type) 값에 따라 정제하도록 수정했으므로, TypeScript는 if 문에서는 event가 HTMLInputElement여야 하며 if 문 이후로는 HTMLElement여야 한다는 사실을 알게 됩니다.
태그는 유니온 타입에서 고유하므로 TypeScript는 둘이 상호 배타적임을 알 수 있습니다.
유니온 타입의 다양한 경우를 처리하는 함수를 구현해야 한다면 태그 된 유니온을 사용합시다:)
'👶 TypeScript' 카테고리의 다른 글
객체 타입의 타입 연산자 - 키인 연산자 (0) | 2023.01.19 |
---|---|
철저 검사(Exhaustiveness Checking)라 불리는 종합성(Totality) (0) | 2023.01.19 |
정제 (0) | 2023.01.19 |
타입 넓히기 - 초과 프로퍼티 확인 (0) | 2023.01.18 |
타입 넓히기 - const 타입 (0) | 2023.01.18 |