Narrowing(내로잉)
TypeScript에서 특정 코드 블록 내에서 변수의 타입을 더 구체적으로 좁히는 것을 의미합니다.
예를 한번 들어보겠습니다.

numberOrString이라는 변수는 number 또는 string이 될 것이라고 선언해 두었습니다.
그리고 값도 입력을 해두었습니다.
이렇게 선언을 하면 string 또는 number가 될 수 있지만 값을 넣어두었기 때문에 마우스를 올려보면 더 구체적인 타입으로 유추되는 것을 확인할 수 있습니다.

이것이 내로잉의 가장 기본이 되는 개념입니다.
우리가 타입을 선언했다고 해도 값을 통해서 TypeScript는 어떤 타입이 될지를 정확하게 유추할 수 있습니다.
Narrowing의 종류
1) Assignment Narrowing
변수에 값을 할당함으로써 타입 좁혀지는 경우입니다.
예를 들어, 아래와 같이 x에 string 값을 할당하여 타입이 유추되는 것을 의미합니다.
let x: string | number;
x = 'hello';
2) typeof Narrowing
typeof 연산자를 사용하여 변수의 타입을 확인하고, 이를 기반으로 타입을 좁힙니다.
예를 들어, 아래와 같이 숫자가 반환될지 스트링이 반환될지 알 수 없는 numbOrString이라는 변수를 선언하겠습니다.
numbOrString = Math.random() > 0.5 ? 1123 : '누구세연';
if (typeof numbOrString === 'string') {
numbOrString;
} else {
numbOrString;
}
만약 numbOrString 변수를 반환받았을 때 string이라는 조건 안의 numbOrString 변수에서는 string type으로 유추됩니다.

그리고 else안에 있는 numbOrString 변수는 number type으로 유추되는 것을 확인할 수 있습니다.
(numbOrString 변수가 number type 또는 string type의 값 밖에 들어올 수 없도록 선언해 두었기 때문입니다.)

3) Truthiness Narrowing
값이 truthy 한 지 여부에 따라 타입을 좁힙니다.
예를 들어, null 또는 string 리스트 둘 중 하나를 반환하는 nullOrString이라는 변수를 선언하겠습니다.
let nullOrString: null | string[] = Math.random() > 0.5 ? null : ['누구세연', '잘자세연'];
if (nullOrString) {
nullOrString;
} else {
nullOrString;
}
만약 nullOrString 값이 null이면 false 아니라면 true를 반환할 것입니다.
if 문 안에 넣었을 때 변수가 true인 경우이기 때문에 string 리스트가 될 수밖에 없습니다.

else 에는 당연히 null값이 들어가게 되는 것을 확인할 수 있습니다.

4) Equality Narrowing
값이 특정 값과 동일한지 여부에 따라 타입을 좁힙니다.
예를 들어, number 또는 string이 들어가는 변수 numberOrString2와 string 또는 boolean 변수 stringOrBool2를 선언해 보겠습니다.
let numbOrString2: number | string = Math.random() > 0.5 ?
1123 : '누구세연';
let stringOrBool2: string | boolean = Math.random() > 0.5 ?
'잘자세연' : true;
if (numbOrString2 === stringOrBool2) {
numbOrString2;
stringOrBool2;
} else {
numbOrString2;
stringOrBool2;
}
if문의 경우 numOrString2와 stringOrBool2가 같은 경우입니다.
둘 다 string 타입인 경우 밖에 없다고 유출할 수 있습니다.


else에는 둘 중에 하나가 String이 아니어도 되니까 number - string 조합도 포함되고 number- boolean 조합도 되기 때문에 union으로 type을 확인할 수 있습니다.


5) in operator Narrowing
객체의 속성이 존재하는지 여부에 따라 타입을 좁힙니다.
예를 들어, 인터페이스를 선언하고 오브젝트를 두 개 만들어줍니다.
interface Human {
name: string;
age: number;
}
interface Dog {
name: string;
type: string;
}
let human: Human = {
name: '누구세연',
age: 25,
}
let dog: Dog = {
name: '오리',
type: 'Yorkshire Terrier',
}
let humanOrDog: Human | Dog = Math.random() > 0.5 ?
human : dog;
if ('type' in humanOrDog) {
humanOrDog;
} else {
humanOrDog;
}
if에 서 type 키 값이 존재하는 값인지 검사해 줍니다. dog에 type이 존재하기 때문에 Dog라는 타입으로 유추됩니다.

else에 속하는 type이라는 키값이 존재하지 않는 Human이라는 타입으로 유추가 되는 것을 확인할 수 있습니다.

이렇게 존재하는 키값의 여부를 in 키워드를 통해 알 수 있습니다.
6) instanceof Narrowing
'instanceof' 연산자를 사용하여 객체가 특정 클래스의 인스턴스인지 여부에 따라 타입을 좁힙니다.
예를 들어, Date 또는 string 타입이 될 수 있는 변수 dateOrString를 선언합니다.
let dateOrString: Date | string = Math.random() > 0.5 ?
new Date() : '누구세연';
if (dateOrString instanceof Date) {
dateOrString;
} else {
dateOrString;
}
if에는 dateOrString 이 Data 클래스의 instance인지 검사해 줍니다. true가 반환된다면 dateOrString 타입이 Date로 유추되는 것을 확인할 수 있습니다.

else에서는 당연히 string 타입입니다.

7) Discriminated Union Narrowing
객체의 프로퍼티를 사용하여 유니온 타입을 좁힙니다.
예를 들어, Human2와 Dog2, Fish2 인터페이스를 선언합니다.
그리고 humanOrDog2 타입을 위의 세 개 인터페이스의 유니온으로 선언합니다.
interface Human2 {
type: 'human';
height: number;
}
interface Dog2 {
type: 'dog',
breed: string;
}
interface Fish2{
type: 'fish';
length: number;
}
type HumanOrDog2 = Human2 | Dog2 | Fish2;
let humanOrDog2: HumanOrDog2 = Math.random() > 0.5 ?
{
type: 'human',
height: 177,
} : Math.random() > 0.5 ? {
type: 'dog',
breed: 'Yorkshire Terrier',
} : {
type: 'fish',
length: 12,
};
if(humanOrDog2.type === 'human'){
humanOrDog2;
}else{
humanOrDog2;
}
if 만약 animal type이 human이라고 했을 때 Human2 타입으로 유추되는 것을 확인할 수 있습니다.

else human 타입이 아닌 두 경우로 유추될 수 있습니다.

8) Exhaustiveness Checking
'switch'문에서 모든 케이스를 다루었는지 컴파일러가 체크하여 빠진 케이스가 없도록 합니다.
예를 들어, 위에 선언한 humanOrDog2.type을 switch 해보겠습니다.
switch(humanOrDog2.type){
case 'human':
humanOrDog2;
break;
case 'dog':
humanOrDog2;
break;
case 'fish':
humanOrDog2;
break;
default:
humanOrDog2;
break;
}
human 인경우는 Human2로 유추되는 것을 확인할 수 있습니다.

dog 인 경우는 Dog2로 유추되는 것을 확인할 수 있습니다.

fish 인 경우는 Fish2로 유추되는 것을 확인할 수 있습니다.

default인 경우 never 타입으로 유추되는 것을 확인할 수 있습니다.
그 어떤 것도 되지 못하는 타입을 never 타입이라고 할 수 있습니다.

클린 코드와 효율적인 프로그래밍을 위해 TypeScript의 내로잉을 잘 이해하고 활용하는 것은 중요합니다!
'Backend > TypeScript' 카테고리의 다른 글
| [TypeScript] 함수 시그니처 타입으로 선언하기 (0) | 2023.12.22 |
|---|---|
| [TypeScript] 함수 정의하기 (0) | 2023.12.16 |
| [TypeScript] Intersection (2) | 2023.11.10 |
| [TypeScript] Union (0) | 2023.11.08 |
| [TypeScript] Casting (캐스팅) (0) | 2023.11.08 |