Type Narrowing 완벽 이해 — TypeScript가 타입을 추론하는 방식

TypeScript를 쓰다 보면 이런 에러, 한 번쯤 본 적 있을 거예요:
function printLength(value: string | number) {
console.log(value.length) // ❌ 'number' 타입에는 'length' 속성이 없습니다.
}“아니, 분명 문자열에도 숫자에도 length가 있을 수 있잖아?”
하지만 TypeScript는 **아직 ‘이 값이 문자열인지 숫자인지 확실하지 않다’**고 판단합니다.
이때 등장하는 개념이 바로 **Type Narrowing (타입 좁히기)**예요.
💡 Type Narrowing이란?
TypeScript는 변수가 여러 타입 중 하나일 수 있을 때,
조건문 등을 통해 그 타입을 “점점 더 구체적으로 좁혀가는” 과정을 수행합니다.
즉, Type Narrowing = TypeScript의 추론 로직이 타입을 점점 구체화하는 과정이에요.
쉽게 말하면, “지금 이 시점에서 이 값이 어떤 타입인지 확실해졌어!”라고 TypeScript가 판단하는 순간이에요.
🧩 기본적인 Narrowing 예시
function printLength(value: string | number) {
if (typeof value === 'string') {
console.log(value.length) // ✅ string으로 좁혀짐
} else {
console.log(value.toFixed(2)) // ✅ number로 좁혀짐
}
}typeof로 조건을 주는 순간,
TypeScript는 각 블록 안에서 value의 타입을 자동으로 좁혀서 추론합니다.
| Narrowing 방법 | 예시 | 설명 |
|---|---|---|
| typeof | typeof value === "string" |
기본 타입 확인 |
| instanceof | value instanceof Date |
클래스 인스턴스 확인 |
| in | 'key' in obj |
속성 존재 여부로 Narrowing |
| equality | value === null, value === undefined |
null/undefined 체크 |
| literal check | value === "success" |
리터럴 값 비교 |
| custom type guard | isUser(obj) |
직접 타입 판별 함수 작성 |
🧠 in 연산자 Narrowing
in은 객체 타입을 구분할 때 매우 자주 쓰여요.
API 응답 객체나 유니언 타입 객체를 다룰 때 특히 유용합니다.
type User = { name: string; email: string }
type Admin = { name: string; permissions: string[] }
function printUser(user: User | Admin) {
if ('permissions' in user) {
console.log('Admin:', user.permissions.join(','))
} else {
console.log('User:', user.email)
}
}이 경우 TypeScript는 조건문 안에서 user를 각각 Admin, User 타입으로 정확히 좁혀줍니다.
🔍 instanceof Narrowing
instanceof는 클래스 기반 객체 판별에 사용됩니다.
class Dog {
bark() { console.log('멍멍!') }
}
class Cat {
meow() { console.log('야옹!') }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark() // ✅ Dog 타입으로 Narrowing
} else {
animal.meow() // ✅ Cat 타입으로 Narrowing
}
}런타임 시점에서도 완벽하게 타입이 구분되는 방식이에요.
🧩 Equality Narrowing (null / undefined 체크)
TypeScript는 null이나 undefined를 체크하면 자동으로 Narrowing해줍니다.
function getLength(value?: string | null) {
if (value == null) return 0 // null 또는 undefined인 경우
return value.length // ✅ string으로 Narrowing
}참고로
== null은null과undefined둘 다 체크합니다.
실무에서는 옵셔널 체이닝(?.)보다 더 정교한 Narrowing이 필요할 때 유용해요.
🎯 Discriminated Union (판별된 유니언)
Type Narrowing의 꽃은 바로 판별된 유니언이에요.
공통 필드(kind, type 등)를 기준으로 각 타입을 구분합니다.
type Circle = { kind: 'circle'; radius: number }
type Square = { kind: 'square'; size: number }
type Shape = Circle | Square
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'square':
return shape.size ** 2
}
}여기서 switch문의 각 케이스 안에서 TypeScript는 shape의 타입을 정확히 좁혀줍니다.
✅
case 'circle'→ Circle 타입
✅case 'square'→ Square 타입
이 패턴은 Redux reducer, API 응답 타입, GraphQL union type에서도 자주 등장해요.
🧠 실무에서의 Type Narrowing 활용
1️⃣ API 응답 타입 처리
type Response = { ok: true; data: string } | { ok: false; error: string }
function handleResponse(res: Response) {
if (res.ok) {
console.log('✅ 성공:', res.data)
} else {
console.error('❌ 실패:', res.error)
}
}2️⃣ Type Guard 함수 직접 만들기
type User = { name: string }
function isUser(obj: any): obj is User {
return typeof obj.name === 'string'
}
const value: unknown = { name: '시황' }
if (isUser(value)) {
console.log(value.name) // ✅ 타입 좁혀짐
}obj is User 구문을 통해 TypeScript에게 직접 타입 정보를 알려줄 수 있습니다.
⚙️ Optional Chaining과 Narrowing의 차이
function getLength(value?: string) {
return value?.length // ✅ 런타임 안전하지만 타입은 여전히 string | undefined
}Optional Chaining은 “안전한 접근”이지 “타입 좁히기”가 아니에요.
Narrowing은 TypeScript의 추론 엔진이 타입 자체를 변경하지만,
Optional Chaining은 코드 실행 시점에서 오류를 방지할 뿐입니다.
📊 정리
| 구분 | 설명 | 예시 |
|---|---|---|
| typeof | 기본 타입 분기 | typeof x === 'string' |
| instanceof | 클래스 판별 | x instanceof Date |
| in | 속성 존재로 분기 | 'id' in obj |
| equality | null, undefined 체크 | x != null |
| discriminated union | 공통 필드 기반 타입 분기 | switch(shape.kind) |
| custom type guard | 직접 정의한 타입 함수 | isUser(obj) |
💬 마무리하며
Type Narrowing은 단순히 문법이 아니라,
TypeScript의 “두뇌” 같은 기능이에요.
이 원리를 이해하면 불필요한 as 캐스팅을 줄이고,
IDE 자동완성도 훨씬 정확하게 활용할 수 있습니다.
결국 TypeScript는 “코드의 흐름”을 이해하는 언어예요.
Narrowing은 그 이해의 과정, 즉 “타입 추론의 핵심 로직”입니다.
#TypeScript #TypeNarrowing #TypeGuard #DiscriminatedUnion #타입스크립트 #타입추론 #TypeSafety #프론트엔드