Skip to content

타입 가드 및 단언

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?.name

11.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 타입을 활용합니다.

TIENIPIA QUALIFIED STANDARD