choco's story

TypeScript [3] - Union 타입 본문

프론트엔드 (FrontEnd)/타입스크립트 (TypeScript)

TypeScript [3] - Union 타입

초코choco 2026. 6. 4. 11:22

Union 타입

Union 타입 시작

유니온(Union) 타입이란?
: 자바스크립트의 OR 연산자(||)와 같이 "A이거나 B이다"라는 의미의 타입

function logText(text: string | number) {
  // ...
}

위 함수의 파라미터인 text에는 문자열 타입(string)이나 숫자 타입(number) 둘 다 올 수 있다.

이렇게 파이프 기호 (|) 연산자를 사용하여 타입을 여러 개 연결하는 방식을 유니온 타입 정의라고 한다.

ex)

let union: string | number;

union = 'Hello World!';
union = 12345;
union = false; // Error

위 예제 코드에서 마지막 union 변수는 선언되지 않은 boolean 타입으로 초기화하려고 했기 때문에 에러가 발생한다.


파이프 기호인 "|"를 통해 타입을 구분하고, 단일 타입인 경우엔 괄호를 사용하지 않아도 된다.

하지만 배열인 경우엔 괄호로 씌워주어야 한다.

여기서 단일 타입이랑 배열이 각각 무슨 뜻인지 헷갈릴 수 있다.

아래 예제 코드를 통해 직접 눈으로 확인하여 보자.

_"문자열이나 숫자가 섞여 들어올 수 있는 배열"_을 유니온 타입을 사용하여 정의하려고 한다.

어떻게 해야 할까?

ex)

// 틀린 예시 : 아래는 단일 문자열 or 숫자 배열 중 택 1
let arr: string | number[];

arr = "Hello"; // 단일 문자열
arr = [1, 2, 3]; // 숫자 배열

arr = [1, "Hello", 2]; // Error

// 옳은 예시 : 아래는 문자열과 숫자가 섞여 들어갈 수 있는 배열
let arr: (string | number)[];

arr = [1, "Hello", 2, "World"];

또는 제네릭 방식으로도 표현 가능하다.
let arr: Array<string | number> = [1, "Hello", 2, "World"];

ex)

function padLeft(value: string, padding: boolean | number) {
  // ...
}

let indentedString = padLeft("Hello world", true);

두 번째 인자는 boolean 또는 number가 들어올 수 있는데, 이 중 boolean을 선택하여 true라는 값을 넣었다.


Union 타입의 장점

아래 두 개의 코드를 비교하여 유니온 타입이 어떤 장점을 갖고 있는지 확인해보자.

// 1) any를 사용하는 경우
function getAge(age: any) {
  // toFixed를 toFixe로 오타냄
  age.toFixed(); // Error, age의 타입이 any로 추론... -> 숫자 관련된 API를 작성할 때 코드 자동 완성 X
  // 컴퓨터는 age가 숫자인지, 문자열인지, 배열인지 구분 불가능
  return age;
}

// 2) Union Type을 사용하는 경우
function getAge(age: number | string) {
  if (typeof age === 'number') {
    age.toFixed(); // 정상 동작, age의 타입이 `number`로 추론... -> 숫자 관련된 API를 쉽게 자동완성 O
    return age;
  }
  if (typeof age === 'string') {
    return age;
  }
  return new TypeError('age must be number or string');
}

any를 사용한다면 어떤 타입인지 제대로 알 수 없다.

그러나 Union Type을 사용한다면 특정 타입(age: number | string) 중 하나라는 것을 알 수 있다.

즉 타입을 제한하여 추론하기 좋고, 자동 완성도 훨씬 쉽게 가능하다는 장점이 있다.


Union 타입의 주의할 점

아래 코드를 먼저 확인해보자.

introduce() 함수의 파라미터 타입을 Person, Developer 타입으로 각각 선언하였는데, 이 둘을 유니온 타입으로 합친 모습이다.

ex)

type Person = {
  name: string;
  age: number;
}

type Developer = {
  name: string;
  skill: string;
}

function introduce(someone: Person | Developer) {
  someone.name; // 정상 동작
  someone.age; // Error, 타입 에러
  someone.skill; // Error, 타입 에러
}

두 Person이랑 Developer 타입이 각각 가진 name, age, skill 속성들도 바로 사용할 수 있을 것이라고 착각하기 쉽다.

그러나 타입스크립트 관점에서는 introduce() 함수를 호출하는 시점에 Person타입이 올지 Developer타입이 올지 알 수가 없기 때문에 에러를 발생시킨다.

어떤 타입이 들어와도 오류가 발생하지 않을 최선의 방향으로 타입을 추론한다.

그러니 Person과 Developer 두 타입에 공통적으로 들어있는 속성인 name만 접근할 수 있게 되는 것이다.


더 나아가면, 함수에서도 리턴값을 유니온으로 사용하게 되면 에러를 발생시킨다.

모든 경우의 수를 고려하여 x와 y 매개변수에 number와 string 둘 중 무엇이 들어오느냐에 따라 리턴값이 달라지니 유니온을 사용했지만, 컴파일러 입장에서는 이러한 방식이 틀린 것이다.

ex)

function add(x: string | number, y: string | number): string | number {
   return x + y;
}

add(1, 2);
add('1', '2');
add(1, '2');

아래 코드처럼 result라는 변수를 만들고 거기에 함수의 결과값을 넣는 방법도 유니온 입장에선 옳은 방법 같지만, 이것도 에러가 발생한다.

ex)

function add(x: string | number, y: string | number): string | number {
   return x + y;
}

const result: string | number = add(1, 2);
result.charAt(1);

왜 에러가 발생할까?

왜냐하면 컴파일러 입장에서는 result 변수가 string인지 number인지 확신이 안된다.

그래서 문자열 메소드인 charAt() 실행에 대해서 에러를 발생시키는 것이다.