긴 타입 정의를 변수처럼 저장하여 재사용하는 기능
// 기본 Type Alias
type Animal = string | number | undefined
let 동물: Animal = 'cat'
// 복잡한 Union 타입을 간단하게
type StringOrNumber = string | number
let value: StringOrNumber = 123
type 사람 = {
name: string,
age: number,
}
let teacher: 사람 = { name: 'john', age: 20 }
// Type Alias 없이 쓰면
let teacher: {
name: string,
age: number,
} = { name: 'john', age: 20 } // 복잡하고 가독성 떨어짐
type Girlfriend = {
readonly name: string,
age: number
}
let 여친: Girlfriend = {
name: '엠버',
age: 25
}
여친.name = '유라' // 에러! readonly 속성
여친.age = 26 // 정상
type Square = {
color?: string, // 선택적 속성
width: number, // 필수 속성
}
let 네모1: Square = { width: 100 } // 정상
let 네모2: Square = { color: 'red', width: 100 } // 정상
중요: color?는 color: string | undefined와 동일
type Name = string
type Age = number
type Person = Name | Age // string | number
let info: Person = 'kim' // 정상
info = 25 // 정상
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 Name = string
type Name = number // 에러! 재정의 불가능
function 내함수(x: number | string) {
return x + 1 // 에러! Union 타입에서는 연산 불가
}
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 // 모든 경우 처리 (권장)
}
}
// 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() // 문자열 메서드 사용 가능
}
function 내함수(x: number | string) {
return (x as number) + 1
}
console.log(내함수(123)) // 124
console.log(내함수('123')) // '1231' (문자열 연결)
// 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) // 타입 안전
// 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
}
특정 값만 가질 수 있도록 제한하는 타입
let john: '대머리' = '대머리'
let kim: '솔로' = '솔로'
// john = '기혼' // 에러! '대머리'만 가능
type Direction = 'left' | 'right' | 'up' | 'down'
let 방향: Direction = 'left'
type Status = 'loading' | 'success' | 'error'
let 현재상태: Status = 'loading'
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
function 가위바위보(choice: '가위' | '바위' | '보'): ('가위' | '바위' | '보')[] {
return ['가위', '바위', '보']
}
// 템플릿 리터럴 타입 (고급)
type Theme = 'light' | 'dark'
type Color = 'red' | 'blue' | 'green'
type ThemedColor = `${Theme}-${Color}` // 'light-red' | 'light-blue' | ... 등
var 자료 = {
name: 'kim'
}
function 내함수(a: 'kim') {
console.log(a)
}
내함수(자료.name) // 에러! string 타입을 'kim' 타입에 할당 불가
var 자료 = {
name: 'kim'
} as const
function 내함수(a: 'kim') {
console.log(a)
}
내함수(자료.name) // 정상! 자료.name은 'kim' 타입
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' 타입
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') // 에러!
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]
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'
// 기본 사용자 정보
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은 선택사항이므로 생략 가능
}
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
}
}
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}`)
}
}
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 메서드 사용 가능
}
}
// 좋은 예
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 // 너무 일반적
// Union (OR): 여러 타입 중 하나
type StringOrNumber = string | number
// Intersection (AND): 모든 타입 속성 포함
type UserWithProfile = User & Profile
// 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() // 런타임 에러 가능성
}