내로잉 (Narrowing)

Changhan·2025년 1월 22일

Typescript

목록 보기
9/29

내로잉은 앞서 배운 타입 추론을 어떻게 가져가는 지에 대한 논리가 들어있기 때문에 매우 중요한 개념이다!!


Narrowing이란?

Union 타입에서 더욱 구체적인 타입으로 논리적으로 유추할 수 있게 되는 것을 의미한다.

간단한 예시를 보자.

let stringOrNumber: number | string = '태연';
stringOrNumber;

위의 코드에서 stringOrNumber은 어떤 타입을 가질까? string 타입

왜 그럴까?

값으로 '태연'이라는 string 값을 넣어버렸기 때문이다. 이것이 바로 Narrowing의 기본이 되는 개념이다.

즉, 타입으로 선언을 했더라도 값을 통해서 해당 변수가 어떤 타입이 될 지를 추론할 수 있다. Narrowing된 타입은 그 타입으로 완전히 인지된다는 것을 기억하자!!


Narrowing의 종류

  1. Assignment Narrowing
  2. typeof Narrowing
  3. Truthiness Narrowing
  4. Equality Narrowing
  5. in operator Narrowing
  6. instanceof Narrowing
  7. discrimated union Narrowing
  8. exhaustiveness Narrowing

1. Assignment Narrowing

특정 값을 할당해서 Narrowing하는 방식, 앞서 예제가 바로 Assignment Narrowing이다.

2. typeof Narrowing

자바스크립트의 typeof 키워드를 이용해 Narrowing하는 방식

let numOrString: number | string = Math.random() > 0.5 ? null : '태연';
if (typeof numOrstring === 'string') {
  numOrstring; // type: string
} else {
  numOrstring; // type: number
};

어떤 타입인지에 따른 조건을 걸어 타입을 다르게 가져갈 수 있는 코드를 작성할 때 유용할 것 같다.

3. Truthiness Narrowing

false로 평가되는 값들의 타입을 체크할 때 사용하는 방식

let nullOrString: null | string = Math.random() > 0.5 ? null : '태연';

if (nullOrString) {
  nullOrString; // type: string
} else {
  nullOrString; // type: string | null
}

의문점

타입이 null | 배열 형태
else의 타입은 null

타입이 null | number 또는 string
else의 타입은 number | null 또는 string | null 이다.

4. Equality Narrowing

같은 지를 비교해서 Narrowing을 하는 방식

let stringOrBoolean: string | boolean = Math.random() > '태연' ? true : '태연';
let stringOrNumber: string | number = Math.random() > 0.5 ? '태연' : 123;


if (stringOrBool2 === stringOrNumber) {
  stringOrBool2; // type: string
  stringOrNumber; // type: string
} else {
  stringOrBool2; // type: string | true, 이 부분 제대로 이해 안됨 (타입이 string | true인 이유)
  stringOrNumber; // type: string | number
}

또 다른 예제를 보자.
let numberOrStringOrNull: number | string | null =
  Math.random() > 0.5 ? 1123 : Math.random() > 0.5 ? '안유진' : null;

if (typeof numberOrStringOrNull === 'number') {
  numberOrStringOrNull; // type: number
} else {
  numberOrStringOrNull; // type: string | null
}

typeof를 쓰면 typeof 내로잉과 equality 내로잉이 동시에 작용한다는 것을 기억하자.

5. in operator Narrowing

자바스크립트의 in 연산자를 이용한 Narrowing 방식

in 연산자는 human 객체에 name이라는 프로퍼티가 있는지 확인하고 싶을 때 사용한다.

console.log('name' in human); // true
interface Human {
  name: string;
  age: number;
}
interface Dog {
  name: string;
  type: string;
}
let human: Human = {
  name: '태연',
  age: 23,
};
let dog: Dog = {
  name: '퍼피',
  type: '시바견',
};

let humanOrDog: Human | Dog = Math.random() > 0.5 ? human : dog;

if ('type' in humanOrDog) {
  humanOrDog; // Dog
} else {
  humanOrDog; // Human
}

humanOrDog에 type 프로퍼티가 있다면 당연히 Dog 타입일 것이다. 그게 아니라면 당연히 Human 타입일 것이다.

6. instanceof Narrowing

자바스크립트의 instanceof 키워드를 이용한 Narrowing 방식

instanceof는 자바스크립트에서 클래스에 속하는지 확인하는 연산자이다.

let dateOrString: Date | string = Math.randome() > 0.5 ? new Date() : '태연';

if (dateOrString instanceof Date) {
  dateOrString; // type: Date
} else {
  dateOrString; // type: string
}

7. Discriminated Union Narrowing

먼저 타입을 선언할 때 나쁜 예시부터 살펴보자.

interface Animal {
  type: 'dog' | 'human';
  height?: number;
  breed?: string;
}

let animal: Animal = Math.random() > 0.5 ? {
  type: 'human',
  height: 177,
} : {
  type: 'dog',
  breed: '포메라니안',
};

if (animal.type === 'human') {
  animal.height; // type: number | undefined
} else {
  animal.breed; // type: string | undefined
  animal.height; // type: number | undefined
};

다음으로 좋은 예시를 살펴보자.

interface Human2 {
  type: 'human';
  height: number;
};
interface Dog2 {
  type: 'dog';
  breed: string;
};

type HumanOrDog = Human2 | Dog2;

let humanOrDog2: HumanOrDog = Math.randome() > 0.5 ? {
  type: 'human',
  height: 177,
} : {
  type: 'dog',
  breed: '포메라니안',
};


if (humanOrDog2.type === 'human') {
  humanOrDog2; // type: Human2
} else {
  humanOrDog2; // type: Dog2
}
  

공통된 프로퍼티를 가지는 객체의 타입을 선언할 때 하나로 묶어서 선언하는 것보다 여러 개로 나눠서 선언하고, 유니언으로 묶어주는 것이 타입을 정확히 추론하는 데에 훨씬 유리하다.

7. Exhaustiveness Checking

checking을 하면서 narrowing을 할 수 있는 방법이다.

switch (humanOrDog2.type) {
  case 'human':
    humanOrDog2; // type: Human2
    break;
  case 'dog':
    humanOrDog2; // type: Dog2
    break;
  default:
    humandOrDog2; // type: never
    
    const _check: never = humanOrDog2;
    break;
};

default 문에서 humandOrDog2의 타입은 Human2도 아니고, Dog2도 아니기 때문에 never 타입이 된다.


다음 코드를 한 번 자세히 살펴보자.

const _check: never = humanOrDog2;

현재 humanOrDog2의 타입은 never인 상태다. 그래서 _check이라는 변수의 타입을 never로 선언해주었다. 이 코드는 추후에 오류 체크용으로 추가한 코드다.

만약에 내가 추후에 이 humanOrDog2에 Fish라는 타입을 하나 더 추가했다고 가정해보자. 그러면 switch 문에서 type의 조건을 확인할 때 default 문에서의 humanOrDog2의 타입은 never가 아닌 Fish 타입이 될 것이다. 그렇게 되면 이 코드에서 빨간 줄의 에러가 날 것이다.


후기

지금까지 배운 다른 개념들 보다 많은 내용을 담고 있지만 하나하나 보면 개념들이 그렇게 크게 어렵지 않았다. 강의를 들으면서 배운 지식들의 연결고리가 계속 머릿 속에 이어지는 느낌을 받아서 다행인 것 같다.

강의를 들으면서 이해하지 못한 내용

  1. Equality Narrowing
    stringOrBool2의 타입이 string | boolean이 아니라 string | true 인 이유

0개의 댓글