Backend/TypeScript

[TypeScript] Narrowing(내로잉)

누구세연 2023. 11. 27. 23:02

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