타입 가드 및 단언
11.4.1. 사용자 정의 타입 가드
is 키워드를 사용하여 타입 가드 함수를 작성합니다.
typescript
interface AdminUser { id: number; name: string; role: 'admin'; permissions: string[] }
interface GuestUser { id: number; name: string; role: 'guest' }
type AppUser = AdminUser | GuestUser
function isAdminUser(user: AppUser): user is AdminUser {
return user.role === 'admin'
}
function renderDashboard(user: AppUser) {
if (isAdminUser(user)) {
console.log(user.permissions) // AdminUser로 좁혀짐
}
}- 타입 가드 함수명은
is접두사로 시작합니다. - 복잡한 조건 분기가 반복되는 경우 타입 가드로 추출하여 재사용합니다.
11.4.2. as 단언 사용 기준
as 타입 단언은 컴파일러의 타입 검사를 우회하므로 사용을 최소화합니다.
허용하는 경우:
typescript
const canvas = document.getElementById('chart') as HTMLCanvasElement
const target = (event as MouseEvent).target as HTMLInputElement금지하는 경우:
typescript
const data = fetchData() as UserProfile // 근거 없는 단언
const value = rawData as any as SpecificType // 이중 단언non-null 단언 연산자(!)는 사용하지 않습니다. 옵셔널 체이닝(?.) 또는 명시적 null 검사로 대체합니다.
typescript
// 잘못된 예
const name = user!.name
// 올바른 예
const name = user?.name11.4.3. unknown vs any
외부에서 유입되는 데이터는 unknown 타입으로 수신한 후 타입 가드로 좁혀서 사용합니다.
typescript
interface ErrorResponse { success: false; message: string }
function isErrorResponse(value: unknown): value is ErrorResponse {
return (
typeof value === 'object' &&
value !== null &&
'success' in value &&
(value as Record<string, unknown>).success === false
)
}
async function handleApiResponse(response: Response): Promise<void> {
const body: unknown = await response.json()
if (isErrorResponse(body)) {
throw new Error(body.message)
}
}any는 타입 검사를 비활성화하지만,unknown은 사용 전에 타입 확인을 강제합니다.catch블록의 에러 객체는unknown타입이므로instanceof검사를 수행합니다.
11.4.4. 타입 좁히기 패턴
TypeScript가 제공하는 타입 좁히기 메커니즘을 상황에 맞게 사용합니다.
| 패턴 | 용도 | 예시 |
|---|---|---|
typeof | 원시 타입 판별 | typeof x === 'string' |
instanceof | 클래스 인스턴스 판별 | error instanceof TypeError |
in | 속성 존재 여부 확인 | 'email' in user |
| 판별 유니온 | 공통 판별 속성으로 분기 | event.type === 'click' |
typescript
// typeof
function formatValue(value: string | number): string {
if (typeof value === 'string') return value.trim()
return value.toFixed(2)
}
// 판별 유니온 (Discriminated Union)
interface TextNotification { type: 'text'; content: string }
interface ImageNotification { type: 'image'; imageUrl: string }
type Notification = TextNotification | ImageNotification
function renderNotification(n: Notification) {
switch (n.type) {
case 'text': return n.content
case 'image': return n.imageUrl
}
}- 판별 유니온은
switch문과 함께 사용하여 모든 케이스를 처리합니다. - 처리되지 않은 케이스를 컴파일 타임에 감지하려면
default에서never타입을 활용합니다.