노트

타입 좁히기와 타입 가드

Narrowing

프런트엔드#ts · 연결된 개념 9개

쉽게 말하면

타입 좁히기는 '문자열이거나 숫자거나 날짜'처럼 넓은 후보를 if 검사를 통과할 때마다 줄여 가는 과정이에요. 스무고개처럼 질문할수록 정체가 분명해져서 그 타입의 메서드를 안전하게 쓸 수 있어요.

비유가 깨지는 곳 직접 만든 is 타입 가드는 질문이 틀려도 컴파일러가 답을 그대로 믿어요. 그래서 가드 함수는 작고 정확하게 써야 하고, typeof null이 'object'인 것 같은 함정도 챙겨야 해요.

타입 좁히기(Narrowing)는 선언된 넓은 타입(유니언, unknown)을 실행 경로를 따라 더 구체적인 타입으로 줄여 가는 과정이다. 그 근거가 되는 런타임 검사를 타입 가드(Type Guard)라고 한다.

function f(x: string | number | Date | null) {
  if (typeof x === 'string') x.toUpperCase()   // typeof
  else if (x instanceof Date) x.getTime()      // instanceof (클래스만 가능)
  else if (x) x.toFixed()                       // truthiness: null 제거
}
if ('swim' in animal) animal.swim()             // in: 속성 유무로 객체 유니언 구분
  • typeof null은 'object'다. null 검사를 따로 한다
  • truthiness 좁히기는 0, '', NaN도 걸러 버리니 숫자·문자열에는 조심한다(옵셔널 체이닝과 null 병합 연산자)
  • interface는 런타임에 없어서 instanceof를 쓸 수 없다. 이때 in이나 구분 필드를 쓴다
  • 할당도 좁히기다. let x: string | number = 1 다음 줄에서 x는 number로 취급된다

사용자 정의 타입 가드 (User-Defined Type Guard, is)

function isSection(item: PageItem): item is SectionItem {
  return item.type === 'section'
}
items.filter(isSection)                        // SectionItem[]로 좁혀짐
items.filter(i => i.type === 'section')        // TS 5.4 이하는 PageItem[] 그대로(5.5부터는 가드로 추론되기도 한다)

함수가 true를 반환하면 그 인자를 해당 타입으로 믿게 하는 문법이다. 검사 내용이 틀려도 컴파일러는 믿으므로 가드 함수는 작고 정확하게 쓴다. 외부 데이터를 unknown으로 받아 좁히는 흐름은 any와 unknown, 구분 필드로 자동 좁히기는 판별 유니언, 같은 구조를 다르게 다루는 방법은 타입 브랜딩 참고. 좁히기를 조기 반환으로 읽기 쉽게 쓰는 방법은 보호 구문.

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 기본형 집착

    전화번호·금액·우선순위 같은 도메인 개념을 끝까지 문자열이나 숫자로만 다루는 냄새. 같은 검증과 포맷 코드가 여기저기 반복되고, 문자열로 모든 걸 표현하는 "stringly typed" 코드가 된다.

  • 매핑된 타입과 keyof

    매핑된 타입(Mapped Types)은 기존 타입의 키를 하나씩 돌며 새 타입을 만드는 문법([K in keyof T]: …)이다. keyof T는 객체 타입의 키 유니언을, T[K](인덱스 접근 타입, Indexed Access Type)는 그 키의 값 타입을 꺼낸다.

  • 템플릿 리터럴 타입

    템플릿 리터럴 타입(Template Literal Types)은 문자열 리터럴 타입을 템플릿 문자열 문법으로 조합해 새 문자열 유니언을 만드는 기능이다. 유니언을 넣으면 가능한 모든 조합이 나온다.

  • 제네릭

    제네릭(Generics)은 타입을 매개변수처럼 받아, 여러 타입에 같은 로직을 재사용하면서도 타입 정보를 잃지 않게 하는 기능이다.

  • tsc --noEmit과 타입 검사 분리

    --noEmit은 tsc에게 타입 검사만 하고 JavaScript 파일은 만들지 말라고 하는 옵션이다. 오류가 있으면 0이 아닌 종료 코드로 실패하고, 없으면 조용히 끝난다.

보기 옵션