SHIN
TypeScript 실전 팁 10선
10편TypeScript의 타입 시스템은 런타임까지 유지되지 않습니다. 외부 API 응답, 사용자 입력 등 런타임 데이터의 타입을 좁힐 때는 타입 가드가 필요합니다.
function format(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase() // string으로 좁혀짐
}
return value.toFixed(2) // number로 좁혀짐
}class ApiError extends Error {
constructor(public code: number, message: string) {
super(message)
}
}
function handleError(err: unknown) {
if (err instanceof ApiError) {
console.error(`API 오류 ${err.code}: ${err.message}`)
} else if (err instanceof Error) {
console.error(err.message)
} else {
console.error('알 수 없는 오류')
}
}interface Cat { meow(): void }
interface Dog { bark(): void }
function makeSound(animal: Cat | Dog) {
if ('meow' in animal) {
animal.meow() // Cat으로 좁혀짐
} else {
animal.bark() // Dog으로 좁혀짐
}
}반환 타입에 parameterName is Type을 명시하면 if 블록 안에서 타입이 자동으로 좁혀집니다.
interface User { type: 'user'; name: string }
interface Admin { type: 'admin'; name: string; permissions: string[] }
type Person = User | Admin
function isAdmin(person: Person): person is Admin {
return person.type === 'admin'
}
function greet(person: Person) {
if (isAdmin(person)) {
// Admin으로 좁혀짐
console.log(`관리자 ${person.name}, 권한: ${person.permissions.join(', ')}`)
} else {
// User로 좁혀짐
console.log(`사용자 ${person.name}`)
}
}interface ApiUser {
id: number
name: string
email: string
}
function isApiUser(data: unknown): data is ApiUser {
return (
typeof data === 'object' &&
data !== null &&
typeof (data as any).id === 'number' &&
typeof (data as any).name === 'string' &&
typeof (data as any).email === 'string'
)
}
async function fetchUser(id: number) {
const res = await fetch(`/api/users/${id}`)
const data: unknown = await res.json()
if (!isApiUser(data)) {
throw new Error('잘못된 API 응답')
}
// 여기서부터 data는 ApiUser
return data
}function assertIsString(val: unknown): asserts val is string {
if (typeof val !== 'string') {
throw new TypeError(`Expected string, got ${typeof val}`)
}
}
function processInput(input: unknown) {
assertIsString(input)
// 여기서부터 input은 string
console.log(input.toUpperCase())
}실제 프로젝트에서는 타입 가드를 직접 작성하기보다 Zod 같은 스키마 라이브러리를 활용합니다.
import { z } from 'zod'
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
})
type User = z.infer<typeof UserSchema> // 스키마에서 타입 자동 추출
const parsed = UserSchema.safeParse(apiResponse)
if (parsed.success) {
console.log(parsed.data.name) // User 타입으로 좁혀짐
}타입 가드는 TypeScript와 런타임 세계 사이의 다리 역할을 합니다. 외부 데이터를 다룰 때는 반드시 검증 후 사용하세요.