유니온 타입(Union Type)
유니온 타입이란, 자바스크립트의 OR 연산자(||
)와 같은 의미를 갖는다.
function union (name: string | Number) {
console.log(typeof name);
}
union('10') // string
union(10) // number
위 함수에 유니온 타입을 적용하면 파라미터 name에는 문자열
과 숫자 타입
모두 올 수 있다. 이처럼 |
연산자를 이용하여 타입을 여러 개 연결하는 방식을 유니온 타입 정의 방식이라고 부른다.
유니온 타입(Union Type)을 사용하는 이유
유니온 타입을 사용하는 이유를 코드를 보면 이해하기 쉽다.
function padLeft(value: string, padding: any) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
padLeft("Hello world", 4); // returns " Hello world"
padLeft("Hello world", '4'); // returns " Hello world"
padLeft("Hello world", true); // Expected string or number, got 'boolean'.
위의 코드를 보면 파라미터 padding에는 string
과 number
타입 둘다 들어올 수 있기 떄문에 any
타입으로 지정했다. 하지만, 문제는 string
, number
타입 이외에 타입이 들어오면 에러를 발생시킨다.
이럴때! 아래와 같이 유니온 타입을 지정해서 사용하면 문제를 해결할 수 있다.
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
padLeft("Hello world", true); // 'boolean' 형식의 인수는 'string | number' 형식의 매개 변수에 할당될 수 없습니다.
공통 타입이 있는 유니온(Union Type)
유니온 타입을 사용할 때 주의해야할 점은 만약 공통적인 타입 유형이 있는 경우에는 모든 유형에 대해 유효한 경우에만 엑세스할 수 있다는 것이다.
아래의 코드를 보면 더 쉽게 이해할 수 있다.
예제1
interface person {
name(): void;
age(): void;
}
interface person2 {
name(): void;
address(): void;
}
declare function getSmallPet(): person | person2;
let human = getSmallPet();
human.name();
// Only available in one of the two possible types
human.address(); // type error
OR 조건이라며? 근데 왜 공통적인 멤버만 엑세스할 수 있는거지?! 라고 생각할 수 있다. 나도 처음엔 전부 사용할 수 있는 줄 알았지만, 타입스크립트는 불확실한 타입에 대해서는 모두 에러로 처리한다.
반대로 생각해보면, age만 들어올 수도 있고 addres만 들어올 수 있는 가능성이 있으므로 타입스크립트는 이러한 애매한 타입에 대해서는 과감하게 배재시키는 것 같다. 따라서, 타입스크립트에서는 좀더 직관적인 관점으로 바라볼 필요가 있다.
예제2
아래 예제 코드도 마찬가지로 Union의 모든 타입이 유효한 경우에만 작업을 허용한다.
예를 들어, string
타입에서만 사용할 수 있는 메서드를 number | string
에서는 사용할 수 없다.
function printId(id: number | string) {
console.log(id.toUpperCase());
}
// 'string | number' 형식에 'toUpperCase' 속성이 없습니다.
// 'number' 형식에 'toUpperCase' 속성이 없습니다.
해결책
해결책은 코드와의 결합을 좁히는 것이다. 좁히는 방법은 타입스크립트의 코드 구조를 기반으로 값에 대해 보다 구체적인 유형을 추론할 수 있도록 해주는 것이다.
예를 들어 타입스크립트 값에는 오직 string
이 있다는 것을 알고 싶다면 typeof "string"
조건을 사용하는 것이다.
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
// 여기는, id는 number 타입이다.
console.log(id)
}
}
printId('string') // STRING
printId(100) // 100
이처럼 해당 타입을 확실히 허용할 수 있는 조건을 걸어주면 해당 타입으로 작업을 진행할 수 있다.
Discriminating Unions
공용체 작업을 위한 일반적인 기술은 타입스크립트가 현재 유형을 좁히는 데 사용할 수 있는 리터럴 타입을 사용하는 단일 필드를 갖는 것이다.
예를 들어, state
라는 단일 공유 필드가 있는 세 가지 유형의 공용체를 만들 것이다.
type LoadingState = {
state: 'loading';
};
type FaildState = {
state: 'failed';
code: number;
};
type SuccessState = {
state: 'success';
response: {
title: string;
duration: number;
summary: string;
}
}
type workState =
| LoadingState
| FaildState
| SuccessState;
위의 모든 유형에는 state
라는 공통의 필드가 있으며 고유한 필드도 존재한다.
LoadingState | FaildState | SuccessState |
---|---|---|
state | state | state |
code | response |
state
필드가 모든 유형에서 공통적으로 들어있다는 것을 감안했을 때, workState
코드에서는 존재 유무 확인없이 엑세스하는 것이 안전하다.
state
를 리터럴 타입으로 사용하면, 해당 문자열과 state
값을 비교할 수 있다.
또한, state는 현재 타입스크립트에서 사용 중인 유형인 것을 알 수 있다.
LoadingState | FaildState | SuccessState |
---|---|---|
“loading” | “failed” | “success” |
이 경우, switch
문을 사용하여 런타임에 표시되는 유형을 좁힐 수 있다.
type workState =
| LoadingState
| FaildState
| SuccessState;
function logger(state: workState): string {
switch (state.state) {
case "loading":
return "Downloading...";
case "failed":
return `Error ${state.code} downloading`;
case "success":
return `Downloaded ${state.response.title} - ${state.response.duration} - ${state.response.summary}`;
}
}
const loading: workState = {state:'loading'};
const failed: workState = {state:'failed', code:200};
const success: workState = {state:'success', response:{title:'test', duration:10, summary:'success!!!'}};
console.log(logger(loading)) // Downloading...
console.log(logger(failed)) // Error 200 downloading
console.log(logger(success)) // Downloaded test - 10 - success!!!
인터섹션 타입(Intersection Type)
인터섹션 타입은 유니온 타입과 다르게 어려 타입을 모두 만족하는 하나의 타입을 의미한다.
아래의 코드를 보자.
interface NetError {
success: boolean;
error?: { message: string };
}
interface networking {
name: string,
}
type networkingResponse = networking & NetError;
const handleNetworking = (response: networkingResponse) => {
if (response.error) {
console.error(response.error.message);
return ;
}
console.log(response.name)
}
const error: networkingResponse = { success: false, error: {message: '500'}, name: '통신'}
const success: networkingResponse = { success: true, name: '통신'}
// error
handleNetworking(error) // 500
//success
handleNetworking(success) // 통신
{
success: boolean;
error?: { message: string };
name: string,
}
이처럼 &
연산자를 이용해 여러 개의 타입 정의를 하나로 합치는 방식을 인터섹션 타입 정의 방식이라고 한다.