[TypeScript-02] Type Aliases

Comely·2025년 6월 6일

TypeScript

목록 보기
2/9

Type Aliases (타입 별칭)

기본 개념

긴 타입 정의를 변수처럼 저장하여 재사용하는 기능

기본 사용법

// 기본 Type Alias
type Animal = string | number | undefined
let 동물: Animal = 'cat'

// 복잡한 Union 타입을 간단하게
type StringOrNumber = string | number
let value: StringOrNumber = 123

Object 타입 저장

type 사람 = {
  name: string,
  age: number,
}

let teacher: 사람 = { name: 'john', age: 20 }

// Type Alias 없이 쓰면
let teacher: {
  name: string,
  age: number,
} = { name: 'john', age: 20 }  // 복잡하고 가독성 떨어짐

readonly 속성

type Girlfriend = {
  readonly name: string,
  age: number
}

let 여친: Girlfriend = {
  name: '엠버',
  age: 25
}

여친.name = '유라'  // 에러! readonly 속성
여친.age = 26       // 정상

선택적 속성 (Optional Properties)

type Square = {
  color?: string,    // 선택적 속성
  width: number,     // 필수 속성
}

let 네모1: Square = { width: 100 }              // 정상
let 네모2: Square = { color: 'red', width: 100 } // 정상

중요: color?color: string | undefined와 동일


Type Aliases 확장

Union으로 합치기

type Name = string
type Age = number
type Person = Name | Age  // string | number

let info: Person = 'kim'  // 정상
info = 25                 // 정상

Intersection으로 합치기 (&)

type PositionX = { x: number }
type PositionY = { y: number }
type XandY = PositionX & PositionY  // { x: number, y: number }

let 좌표: XandY = { x: 1, y: 2 }

복합 확장

type BasicInfo = { name: string, age: number }
type ContactInfo = { phone: string, email: string }
type UserInfo = BasicInfo & ContactInfo & { isActive: boolean }

let user: UserInfo = {
  name: 'kim',
  age: 25,
  phone: '010-1234-5678',
  email: 'kim@email.com',
  isActive: true
}

Type Aliases 제한사항

type Name = string
type Name = number  // 에러! 재정의 불가능

Type Narrowing (타입 좁히기)

문제 상황

function 내함수(x: number | string) {
  return x + 1  // 에러! Union 타입에서는 연산 불가
}

typeof를 사용한 Narrowing

function 내함수(x: number | string) {
  if (typeof x === 'number') {
    return x + 1        // x는 number 타입
  } else if (typeof x === 'string') {
    return x + '1'      // x는 string 타입
  } else {
    return 0            // 모든 경우 처리 (권장)
  }
}

다양한 Narrowing 방법

// 1. typeof 사용
function checkType(x: string | number) {
  if (typeof x === 'string') {
    return x.toUpperCase()  // string 메서드 사용 가능
  }
  return x * 2  // number 연산 가능
}

// 2. in 연산자 사용
type Dog = { bark: string }
type Cat = { meow: string }

function makeSound(animal: Dog | Cat) {
  if ('bark' in animal) {
    console.log(animal.bark)  // Dog 타입
  } else {
    console.log(animal.meow)  // Cat 타입
  }
}

// 3. instanceof 사용
function processDate(date: Date | string) {
  if (date instanceof Date) {
    return date.getFullYear()  // Date 메서드 사용 가능
  }
  return new Date(date).getFullYear()
}

// 4. Array.isArray() 사용
function processValue(value: string | string[]) {
  if (Array.isArray(value)) {
    return value.join(', ')    // 배열 메서드 사용 가능
  }
  return value.toUpperCase()   // 문자열 메서드 사용 가능
}

Type Assertion (타입 단언)

기본 사용법

function 내함수(x: number | string) {
  return (x as number) + 1
}

console.log(내함수(123))  // 124
console.log(내함수('123'))  // '1231' (문자열 연결)

Type Assertion 특징

  1. 타입 강제 변환: Union 타입을 특정 타입으로 단언
  2. 컴파일 타임만 적용: 실제 런타임 동작은 변경되지 않음
  3. 제한적 사용: 관련 있는 타입끼리만 단언 가능

안전한 사용 예시

// DOM 요소 타입 단언
let button = document.querySelector('#btn') as HTMLButtonElement
button.disabled = true  // HTMLButtonElement 메서드 사용 가능

// API 응답 타입 단언
let response = JSON.parse('{"name":"kim","age":25}') as {name: string, age: number}
console.log(response.name)  // 타입 안전

Type Assertion vs Narrowing

// Narrowing (권장)
function safeAdd(x: number | string) {
  if (typeof x === 'number') {
    return x + 1
  }
  return parseFloat(x) + 1
}

// Assertion (주의해서 사용)
function riskyAdd(x: number | string) {
  return (x as number) + 1
}

Literal Types (리터럴 타입)

기본 개념

특정 값만 가질 수 있도록 제한하는 타입

문자열 Literal Type

let john: '대머리' = '대머리'
let kim: '솔로' = '솔로'

// john = '기혼'  // 에러! '대머리'만 가능

Union Literal Type

type Direction = 'left' | 'right' | 'up' | 'down'
let 방향: Direction = 'left'

type Status = 'loading' | 'success' | 'error'
let 현재상태: Status = 'loading'

숫자 Literal Type

type DiceNumber = 1 | 2 | 3 | 4 | 5 | 6
let 주사위: DiceNumber = 3

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

함수에서 Literal Type 활용

// 매개변수와 반환값 모두 Literal Type
function 가위바위보(choice: '가위' | '바위' | '보'): ('가위' | '바위' | '보')[] {
  return ['가위', '바위', '보']
}

// 템플릿 리터럴 타입 (고급)
type Theme = 'light' | 'dark'
type Color = 'red' | 'blue' | 'green'
type ThemedColor = `${Theme}-${Color}`  // 'light-red' | 'light-blue' | ... 등

as const 문법

문제 상황

var 자료 = {
  name: 'kim'
}

function 내함수(a: 'kim') {
  console.log(a)
}

내함수(자료.name)  // 에러! string 타입을 'kim' 타입에 할당 불가

as const로 해결

var 자료 = {
  name: 'kim'
} as const

function 내함수(a: 'kim') {
  console.log(a)
}

내함수(자료.name)  // 정상! 자료.name은 'kim' 타입

as const 효과

  1. Literal 타입 적용: 값 자체가 타입이 됨
  2. readonly 적용: 모든 속성이 변경 불가능
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
} as const

// config.apiUrl = 'other'  // 에러! readonly 속성

type ApiUrl = typeof config.apiUrl  // 'https://api.example.com' 타입

배열에서 as const

const colors = ['red', 'green', 'blue'] as const
type Color = typeof colors[number]  // 'red' | 'green' | 'blue'

function setColor(color: Color) {
  console.log(color)
}

setColor('red')    // 정상
setColor('yellow') // 에러!

실전 예제

예제 1: 데이터 클리닝 함수

function 클리닝함수(a: (number | string)[]): number[] {
  let 클리닝완료된거: number[] = []

  a.forEach((b) => {
    if (typeof b === 'string') {
      클리닝완료된거.push(parseFloat(b))
    } else {
      클리닝완료된거.push(b)
    }
  })

  return 클리닝완료된거
}

console.log(클리닝함수([123, '456', 789, '012']))  // [123, 456, 789, 12]

예제 2: 선생님 과목 조회 함수

type Teacher = {
  subject: string | string[]
}

function 마지막과목찾기(teacher: Teacher): string {
  if (typeof teacher.subject === 'string') {
    return teacher.subject
  } else if (Array.isArray(teacher.subject)) {
    return teacher.subject[teacher.subject.length - 1]
  } else {
    return '과목 없음'
  }
}

let 철수쌤 = { subject: 'math' }
let 영희쌤 = { subject: ['science', 'english'] }
let 민수쌤 = { subject: ['science', 'art', 'korean'] }

console.log(마지막과목찾기(철수쌤))  // 'math'
console.log(마지막과목찾기(영희쌤))  // 'english'
console.log(마지막과목찾기(민수쌤))  // 'korean'

예제 3: 사용자 타입 확장

// 기본 사용자 정보
type User = {
  name: string,
  email?: string,
  phone: string
}

// 성인 여부 추가
type Adult = {
  adult: boolean
}

// 두 타입 결합
type NewUser = User & Adult

let 회원가입정보: NewUser = {
  name: 'kim',
  adult: false,
  phone: '010-1234-5678'
  // email은 선택사항이므로 생략 가능
}

고급 패턴

1. 조건부 타입 활용

type ApiResponse<T> = {
  success: true,
  data: T
} | {
  success: false,
  error: string
}

function handleResponse<T>(response: ApiResponse<T>): T | null {
  if (response.success) {
    return response.data  // 타입 안전
  } else {
    console.error(response.error)
    return null
  }
}

2. Discriminated Union

type Shape = 
  | { kind: 'circle', radius: number }
  | { kind: 'square', size: number }
  | { kind: 'rectangle', width: number, height: number }

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.size ** 2
    case 'rectangle':
      return shape.width * shape.height
    default:
      // 모든 케이스를 처리했는지 컴파일 타임에 확인
      const exhaustiveCheck: never = shape
      throw new Error(`Unhandled shape: ${exhaustiveCheck}`)
  }
}

3. Type Guard 함수

function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function isNumber(value: unknown): value is number {
  return typeof value === 'number'
}

function processValue(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase())  // string 메서드 사용 가능
  } else if (isNumber(value)) {
    console.log(value.toFixed(2))     // number 메서드 사용 가능
  }
}

모범 사례 및 팁

1. Type Alias 네이밍

// 좋은 예
type UserProfile = { name: string, age: number }
type ApiResponse<T> = { data: T, status: number }
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'

// 피해야 할 예
type user = { name: string, age: number }  // 소문자 시작
type Data = any  // 너무 일반적

2. Union vs Intersection 선택

// Union (OR): 여러 타입 중 하나
type StringOrNumber = string | number

// Intersection (AND): 모든 타입 속성 포함
type UserWithProfile = User & Profile

3. Narrowing vs Assertion 선택

// Narrowing 선호 (안전)
function processId(id: string | number) {
  if (typeof id === 'string') {
    return id.toUpperCase()
  }
  return id.toString()
}

// Assertion 최소화 (위험)
function processId(id: string | number) {
  return (id as string).toUpperCase()  // 런타임 에러 가능성
}

실전 체크리스트

Type Aliases

  • 복잡한 타입을 Type Alias로 분리
  • 의미 있는 이름으로 명명
  • readonly와 선택적 속성 적절히 활용
  • Intersection으로 타입 확장

Type Narrowing

  • Union 타입 사용 시 적절한 타입 가드 구현
  • typeof, in, instanceof 등 적절한 방법 선택
  • 모든 케이스 처리 확인

Type Assertion

  • 최소한으로 사용
  • 타입 안전성이 확실할 때만 사용
  • DOM 조작이나 외부 라이브러리 사용 시 활용

Literal Types

  • 제한된 값만 허용해야 하는 경우 활용
  • 상수나 설정값에 활용
  • as const로 불변 객체 생성
profile
App, Web Developer

0개의 댓글