TypeScript 독학 가이드 7 - 타입 별칭과 유니온 타입

learning by Seven Fingers Studio 18분
TypeScript타입별칭유니온타입인터섹션타입웹개발

“이 함수는 문자열이나 숫자를 받아요” 이런 경우 어떻게 타입을 지정할까요? 유니온 타입이 답이에요. 그리고 긴 타입을 매번 쓰기 귀찮을 때는? 타입 별칭을 쓰면 됩니다. 오늘은 TypeScript의 고급 타입 기능들을 알아볼게요.

타입 별칭 (Type Alias)

긴 타입을 짧은 이름으로 만드는 거예요. type 키워드를 사용합니다.

type UserID = number;
type UserName = string;

let id: UserID = 123;
let name: UserName = "김철수";

console.log(id);
console.log(name);

실행 결과:

✓ 컴파일 성공
123
김철수

타입 별칭으로 의미를 명확하게 표현할 수 있습니다

객체 타입 별칭

객체 타입도 별칭으로 만들 수 있어요:

type User = {
  id: number;
  name: string;
  email: string;
};

const user: User = {
  id: 1,
  name: "김철수",
  email: "kim@example.com"
};

function printUser(user: User) {
  console.log(`ID: ${user.id}, 이름: ${user.name}`);
}

printUser(user);

실행 결과:

✓ 컴파일 성공
ID: 1, 이름: 김철수

복잡한 객체 타입을 재사용할 수 있습니다

유니온 타입 (Union Type)

“A 또는 B” 이런 식으로 여러 타입 중 하나를 받을 수 있어요. | 기호를 사용합니다.

type ID = number | string;

function printID(id: ID) {
  console.log(`ID: ${id}`);
}

printID(123);
printID("ABC123");
printID(true);  // 에러! boolean은 안 됨

실행 결과:

✓ 처음 두 호출 성공
ID: 123
ID: ABC123
✗ 타입 에러
Argument of type 'boolean' is not assignable to parameter of type 'ID'

number 또는 string만 허용됩니다

타입 가드

유니온 타입 사용할 때 타입을 구분해야 할 때가 있어요:

function processValue(value: number | string) {
  if (typeof value === "string") {
    console.log(`문자열 길이: ${value.length}`);
  } else {
    console.log(`숫자 2배: ${value * 2}`);
  }
}

processValue("TypeScript");
processValue(10);

실행 결과:

✓ 컴파일 성공
문자열 길이: 10
숫자 2배: 20

typeof로 타입을 체크하고 각각 다르게 처리합니다

typeof로 타입을 확인하면 TypeScript가 그 블록 안에서는 타입을 좁혀줘요. 이걸 “타입 가드”라고 합니다.

배열 유니온

type StringOrNumber = string | number;
type ArrayOfStringOrNumber = StringOrNumber[];

const values: ArrayOfStringOrNumber = [1, "hello", 2, "world"];

values.forEach(value => {
  if (typeof value === "string") {
    console.log(`문자열: ${value}`);
  } else {
    console.log(`숫자: ${value}`);
  }
});

실행 결과:

✓ 컴파일 성공
숫자: 1
문자열: hello
숫자: 2
문자열: world

문자열과 숫자가 섞인 배열을 처리할 수 있습니다

인터섹션 타입 (Intersection Type)

“A 그리고 B” 두 타입을 합치는 거예요. & 기호를 사용합니다.

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

type Employee = {
  employeeId: string;
  department: string;
};

type WorkingPerson = Person & Employee;

const worker: WorkingPerson = {
  name: "김철수",
  age: 25,
  employeeId: "E001",
  department: "개발팀"
};

console.log(worker);

실행 결과:

✓ 컴파일 성공
{ name: '김철수', age: 25, employeeId: 'E001', department: '개발팀' }

두 타입의 모든 속성을 가져야 합니다

WorkingPerson은 Person의 속성 + Employee의 속성을 전부 가져야 해요.

리터럴 타입 (Literal Type)

정확한 값 자체를 타입으로 쓸 수 있어요.

문자열 리터럴

type Direction = "up" | "down" | "left" | "right";

function move(direction: Direction) {
  console.log(`${direction} 방향으로 이동`);
}

move("up");
move("down");
move("diagonal");  // 에러!

실행 결과:

✓ 처음 두 호출 성공
up 방향으로 이동
down 방향으로 이동
✗ 타입 에러
Argument of type '"diagonal"' is not assignable to parameter of type 'Direction'

정확히 지정된 값만 사용할 수 있습니다

정확히 “up”, “down”, “left”, “right” 중 하나만 받아요. 다른 문자열은 에러!

숫자 리터럴

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(): DiceRoll {
  return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}

const result = rollDice();
console.log(`주사위 결과: ${result}`);

실행 결과:

✓ 컴파일 성공
주사위 결과: 4

1부터 6까지의 숫자만 반환합니다

불린 리터럴

type Success = true;
type Failure = false;

function isSuccess(result: Success | Failure): string {
  return result ? "성공!" : "실패...";
}

console.log(isSuccess(true));
console.log(isSuccess(false));

실행 결과:

✓ 컴파일 성공
성공!
실패...

true 또는 false 리터럴 타입을 사용할 수 있습니다

객체 리터럴 타입

객체의 정확한 구조를 타입으로 지정할 수 있어요:

type Config = {
  readonly apiUrl: string;
  timeout: number;
  retryCount?: number;
};

const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000
};

console.log(config);
// config.apiUrl = "다른 URL";  // 에러! readonly

실행 결과:

✓ 컴파일 성공
{ apiUrl: 'https://api.example.com', timeout: 5000 }

readonly 속성은 수정할 수 없습니다

타입 별칭 vs 인터페이스

“언제 type 쓰고 언제 interface 써요?” 자주 받는 질문이에요.

type으로만 가능한 것

// 유니온 타입
type ID = number | string;

// 튜플
type Point = [number, number];

// 프리미티브 타입 별칭
type Name = string;

interface로만 가능한 것

// 선언 병합
interface User {
  name: string;
}
interface User {
  age: number;
}
// 자동으로 합쳐짐

공통점

객체 타입은 둘 다 가능:

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

interface UserInterface {
  name: string;
  age: number;
}

추천: 객체는 interface, 유니온/튜플은 type!

실전 예제: HTTP 응답 타입

API 작업할 때 유용한 패턴이에요:

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

type SuccessResponse<T> = {
  success: true;
  data: T;
};

type ErrorResponse = {
  success: false;
  error: {
    code: string;
    message: string;
  };
};

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

type User = {
  id: number;
  name: string;
  email: string;
};

function fetchUser(id: number): ApiResponse<User> {
  // 성공 케이스
  return {
    success: true,
    data: {
      id: 1,
      name: "김철수",
      email: "kim@example.com"
    }
  };
}

const response = fetchUser(1);

if (response.success) {
  console.log(`사용자: ${response.data.name}`);
} else {
  console.log(`에러: ${response.error.message}`);
}

실행 결과:

✓ 컴파일 성공
사용자: 김철수

유니온 타입으로 성공/실패 케이스를 명확히 구분했습니다

실전 예제: 상태 관리

React나 Vue 같은 프레임워크에서 자주 쓰는 패턴:

type LoadingState = {
  status: "loading";
};

type SuccessState<T> = {
  status: "success";
  data: T;
};

type ErrorState = {
  status: "error";
  error: string;
};

type State<T> = LoadingState | SuccessState<T> | ErrorState;

function handleState(state: State<string>) {
  switch (state.status) {
    case "loading":
      console.log("로딩 중...");
      break;
    case "success":
      console.log(`데이터: ${state.data}`);
      break;
    case "error":
      console.log(`에러: ${state.error}`);
      break;
  }
}

handleState({ status: "loading" });
handleState({ status: "success", data: "완료!" });
handleState({ status: "error", error: "네트워크 오류" });

실행 결과:

✓ 컴파일 성공
로딩 중...
데이터: 완료!
에러: 네트워크 오류

상태에 따라 타입이 자동으로 좁혀집니다

status로 switch하면 TypeScript가 각 case에서 타입을 자동으로 좁혀줘요. 엄청 편합니다!

정리하면

  • 타입 별칭: type 키워드로 타입에 이름 붙이기
  • 유니온 타입: |로 “또는” 표현
  • 인터섹션 타입: &로 “그리고” 표현
  • 리터럴 타입: 정확한 값을 타입으로 사용
  • 타입 가드: typeof, switch 등으로 타입 좁히기

다음 글에서는 제네릭에 대해 배워볼게요. 함수나 클래스를 만들 때 타입을 유연하게 받는 방법입니다!


다음 글 보기

← 이전 글
TypeScript 독학 가이드 6 - 클래스
다음 글 →
TypeScript 독학 가이드 8 - 제네릭
← 블로그 목록으로