노트

타입 좁히기와 타입 가드

Narrowing

프론트엔드#ts · 연결된 개념 10개

쉽게 말하면

타입 좁히기는 '문자열이거나 숫자거나 날짜'처럼 넓은 후보를 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로 취급된다
  • non-null 단언 x!은 좁히기가 아니라 단언이다. 검사 없이 타입에서 null과 undefined만 지우고, 런타임 동작은 바꾸지 않는다. 그래서 값이 null이 아님을 확실히 알 때만 쓴다. 확실하지 않으면 if (x != null)로 검사해 좁히거나 x ?? 기본값으로 대신할 값을 준다

사용자 정의 타입 가드 (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, 구분 필드로 자동 좁히기는 판별 유니언, 같은 구조를 다르게 다루는 방법은 타입 브랜딩 참고. 좁히기를 조기 반환으로 읽기 쉽게 쓰는 방법은 보호 구문.

출처: TypeScript Handbook - Narrowing · Non-null Assertion Operator · TypeScript 5.5 - Inferred Type Predicates

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 기본형 집착

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

  • 제네릭

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

  • satisfies 연산자

    satisfies(TypeScript 4.9+)는 값이 어떤 타입을 만족하는지 검사하되, 값에서 추론된 구체적인 타입은 그대로 유지하는 연산자다.

  • 매핑된 타입과 keyof

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

  • tsconfig 핵심 옵션

    tsconfig.json은 TypeScript 컴파일러(tsc)에게 어떤 파일을, 어떤 규칙으로 검사하고, 무엇을 출력할지 알려 주는 프로젝트 설정 파일이다. tsc --init으로 만들고, 이 파일이 있으면 tsc만 쳐도 프로젝트 전체를 컴파일한다(-w는 감시 모드, watch mode).

보기 옵션