← 전체 글로 돌아가기

TypeScript

TypeScript 타입 가드가 제대로 작동하지 않을 때

컴파일은 되는데 런타임에 타입 에러가 나거나, 타입 가드 로직이 누락될 때의 진단법.

TypeScript 타입 가드는 런타임 체크와 컴파일 타임 타입 좁히기를 동시에 해야 한다. 하나라도 빼면 문제가 생긴다.

내가 자주 하는 실수는 타입 가드 함수를 만들고도 런타임에 제대로 검증하지 않거나, 검증은 하는데 타입으로 narrowing하지 않는 경우다.

1단계: 타입 가드 함수 검증

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    typeof obj.id === 'string' &&
    typeof obj.name === 'string'
  )
}

여기서 가장 흔한 실수:

  • obj !== null 체크 빼먹기. JavaScript에서 typeof null === 'object'이기 때문
  • in 연산자로 프로퍼티 존재 확인 후, 따로 타입 체크 안 하기
npx tsc --noEmit

먼저 컴파일 에러가 없는지 본다.

2단계: 런타임 테스트

// 각 케이스별로 테스트
console.log(isUser({ id: '123', name: 'john' })) // true
console.log(isUser({ id: '123', name: 123 })) // false
console.log(isUser(null)) // false
console.log(isUser(undefined)) // false
console.log(isUser('string')) // false

가드 함수가 예상대로 true/false를 반환하는지 확인한다.

가드 함수가 맞다면, 다음은 타입 narrowing이다.

3단계: 타입 narrowing 확인

const data: unknown = fetchUser()

if (isUser(data)) {
  // 여기서 data는 User 타입으로 narrowed
  console.log(data.id) // OK
} else {
  // 여기서는 unknown
  console.log(data.id) // TS 에러
}

if 블록 안에서 dataUser 타입으로 인식되는지 본다.

만약 여전히 unknown이라면:

  • 가드 함수의 반환 타입이 boolean으로 돼 있음 (obj is User가 아니라)
  • TypeScript 버전이 오래됐을 수도

4단계: 복잡한 타입의 경우 재귀 검증

interface Post {
  id: string
  author: User
  tags: string[]
}

function isPost(obj: unknown): obj is Post {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'author' in obj &&
    'tags' in obj &&
    typeof obj.id === 'string' &&
    isUser(obj.author) && // 재귀 호출
    Array.isArray(obj.tags) &&
    obj.tags.every((t: unknown) => typeof t === 'string')
  )
}

중첩된 객체나 배열이 있으면 재귀적으로 검증해야 한다.

Array.isArray() 후에 every()로 배열 요소도 체크한다.

5단계: 옵셔널 필드 처리

interface User {
  id: string
  name: string
  email?: string // optional
}

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    typeof obj.id === 'string' &&
    typeof obj.name === 'string' &&
    (obj.email === undefined || typeof obj.email === 'string')
  )
}

옵셔널 필드는 undefined이거나 정확한 타입이어야 한다.

타입 가드 외에 다른 방법들

간단한 경우 as const 단언으로 충분할 수도:

const user = response.data as const satisfies User

하지만 API 응답처럼 신뢰할 수 없는 데이터는 반드시 가드 함수를 거쳐야 한다.

빌드 후 확인해야 할 것

npm run build
npm run test  # 타입 가드 테스트 케이스 포함

GitHub Actions 같은 CI에서 이 단계를 빠뜨리면, 배포 후에야 타입 에러를 발견한다. 가드 함수는 단위 테스트 필수다.