TypeScript: 타입 좁히기(Type Narrowing) 가이드

ε( ε ˙³˙)з ○º·2025년 6월 20일
post-thumbnail

Intro

TypeScript에서 유니언 타입(|)을 사용할 때는 런타임 시점에 타입을 명확하게 판별해 주는 타입 좁히기(Narrowing)가 필요해요. 이를 통해 코드의 안정성과 가독성을 높이고, 불필요한 타입 단언을 줄일 수 있습니다.

이번 글에서는 TypeScript에서 자주 사용되는 대표적인 타입 좁히기 기법들을 정리해보았어요.


타입 좁히기란?

타입 좁히기(Narrowing)란, TypeScript에서 유니언 타입 등 복수의 가능성을 가진 값에 대해
조건문, 제어 흐름, 연산자 등을 활용해 보다 구체적인 단일 타입으로 한정하는 과정을 의미해요.

타입이 불명확한 변수를 실행 시점(런타임)에 어떤 타입인지 명확히 판단할 수 있도록 TypeScript 컴파일러가 추론 범위를 좁혀주는 기능입니다.

✅ 예제: 사용자 입력 처리

실제 개발을 하다보면 사용자의 입력값이 문자열 또는 숫자로 혼합되어 들어오는 경우가 자주 발생합니다.

예를 들어 나이를 입력받는 상황에서 사용자가 "27", " 27 ", 27 등 다양한 형태로 입력할 수 있다고 가정해보 았을 때, 입력값을 숫자로 안전하게 변환하고 유효성을 검사하기 위해 타입 좁히기가 필요해요.

🧐 위 예시처럼 사용자 입력 데이터의 정확한 타입을 확인하고 안전하게 처리하는 과정에서 Narrowing 기법을 활용하는 상황들을 자주 접할 수 있어요.

타입 좁히기(Type Narrowing)의 대표적인 4가지 방법

◻️ Tyepof 연산자로 원시 타입 좁히기

typeof는 JavaScript의 내장 연산자로, 런타임에 값의 원시 타입(Primitive Type)을 판별할 수 있습니다.
TypeScript에서는 typeof 연산자를 활용해 유니언 타입 중 특정 타입을 판별하고 좁히는 데 사용합니다.

  • 반환 가능한 값: string, number, boolean, object, undefined, function, symbol, bigint

🧐 이처럼 typeof value === "string"과 같은 조건문을 통해 TypeScript가 value의 타입을 string 또는 number로 좁혀 추론합니다. 덕분에 별도의 타입 단언 없이도 안전하게 속성 접근이 가능해져요.


◻️in 연산자로 속성 존재 여부로 좁히기

in 연산자는 객체가 특정 프로퍼티를 가지고 있는지를 확인하는 JavaScript 내장 연산자입니다.
TypeScript에서는 이를 활용해 객체 형태의 유니언 타입을 조건 분기하여 좁히는 데 유용하게 사용할 수 있어요.

  • 객체가 특정 속성을 가지고 있는지 런타임에서 판별
  • hasOwnProperty()와 유사한 원리로 작동
  • 별도의 타입 단언 없이 속성 존재 여부만으로 타입 추론 가능

✅ 예제: 사용자 정보에 따른 UI 분기 처리

예를 들어 백오피스 화면에서, 사용자와 관리자의 데이터를 구분해 각각 다른 정보를 표시해야 하는 상황을 생각해볼 수 있어요. 아래 예제에서는 "email" 속성의 존재 여부를 기준으로 User 타입과 Admin 타입을 구분합니다.

🧐 이처럼 in 연산자를 사용하면 별도의 추가 타입 검사 없이도 안전하게 타입을 좁히고 속성 접근이 가능해요.

◻️ instanceof 연산자로 클래스 인스턴스 좁히기

instanceof는 객체가 특정 클래스의 인스턴스인지를 런타임에서 프로토타입 체인을 따라 확인하는 연산자입니다.
TypeScript에서는 이 연산자를 활용해 클래스 기반 객체를 조건 분기하고 타입을 좁힐 수 있습니다.

이 방식은 Custom Error Class를 활용해 다양한 에러를 구분하는 패턴에서 자주 사용됩니다.

✅ 예제: 사용자 정의 에러 타입 처리

🧐 실무 환경에서는 다양한 에러가 발생할 수 있기 때문에 에러의 원인에 따라 명확한 메시지와 적절한 대응 로직이 필요해요. 이때 instanceof를 활용하면 에러 객체의 타입을 정확히 구분하고 각 타입에 맞는 처리 흐름을 안전하게 분기할 수 있습니다.


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

사용자 정의 타입 가드는 typeof, in, instanceof 같은 내장 타입 좁히기 연산자만으로는 충분히 타입을 판별할 수 없는 경우, 개발자가 직접 타입 판별 함수를 만들어 TypeScript 컴파일러에 "이 값은 이 타입이이야~"라고 명시적으로 알려주는 방식이에요.

🤔 왜 필요할까?

TypeScript는 typeof, in 등을 통해 기본적인 타입 판별은 가능하지만 복잡한 유니언 타입이나 구조가 유사한 객체는 단순 조건만으로 구분하기 어려워요. 이럴 때 사용자 정의 타입 가드를 활용하면 정밀한 타입 분기가 가능합니다.

🔍 문법: parameter is Type 형태

  • animal is Dog 타입 가드 시그니처로 함수가 true를 반환하면, TypeScript는 animal이 Dog 타입임을 확신하고 조건문 내부에서 타입을 안전하게 좁혀줍니다

✅ 예제: Notification 시스템에서 타입 구분하기

예를 들어 알림(Notification) 시스템에서 여러 타입의 알림 객체가 있을때 알림을 전송하는 함수에서는 type 프로퍼티만 가지고는 조건이 불명확하거나 불충분한 경우가 있어요.

이처럼 구조가 유사한 객체들을 기본 연산자만으로 구분하기 어려운 상황에서는 사용자 정의 타입 가드를 통해 타입 추론을 명확히 유도할 수 있습니다. 또한 as 같은 타입 단언 없이도 안전한 조건 분기가 가능해요.


🚨 Narrowing이 없는 경우 발생 가능한 문제

TypeScript를 사용하는 가장 큰 이유는 컴파일 타임에 타입을 검증하여 런타임 오류를 예방하는 데 있습니다.
하지만 타입 좁히기(Narrowing)를 적용하지 않으면 컴파일러가 타입을 정확히 추론하지 못해 잠재적인 런타임 오류나 불안정한 코드가 발생할 수 있어요.

이처럼 타입 체크 없이 프로퍼티나 메서드에 접근하면 정상적으로 컴파일되더라도 실행 중 오류가 발생할 수 있습니다. 타입이 불명확한 상황에서는 분기 조건이 잘못 동작할 수 있고 억지로 as를 사용해 타입 단언을 남용하게 되는 경우도 생깁니다.

결과적으로 TypeScript의 타입 시스템을 온전히 활용하지 못해 코드의 신뢰도와 유지보수성이 전반적으로 낮아질 수 있어요.


💭 마무리하며

타입 좁히기는 예측 가능한 로직과 안정적인 코드 작성을 위한 설계 요소로 서비스 환경에서는 사소한 타입 오류도 런타임 오류로 이어질 수 있기 때문에 조건 분기마다 타입을 명확히 좁혀주는 습관이 필요합니다. 💪🏻


📚 Reference


이 글은 공식 문서를 기반으로 내용을 정리한 포스팅입니다.
혹시 내용 중 틀린 부분이나 보완할 부분이 있다면 댓글로 남겨주시면 감사하겠습니다. 🙏🏻

profile
차곡차곡 쌓아두기 💭

0개의 댓글