Type Narrowing-1(typeof guard, truthiness guard, equlality narrowing, in operator narrowing, instanceof narrowing)

GI JUNG·2022년 12월 14일
3

typescript

목록 보기
2/10
post-thumbnail

typescript를 사용하면서 type에 따라 사용할 수 있는 property들도 다르며 type 추론이 불가한 인자 또는 type이 여러개일 수 있는 인자(ex -> union type)를 함수에 넘겨주면 typescript는 해당 인자의 type infer가 불가능해 property를 자동으로 불러올 수 없다. 또한 typescript는 type을 모르기 때문에 정적검사(🤔)시에 즉각적으로 오류를 보여줄 수 없다. 이게 내가 생각한 type narrowing이 필요한 이유라 생각된다.

즉 정리하자면 type narrowing이 필요한 경우를 아래 2가지로 추려봤다.

1️⃣ type추론이 불가하거나 type이 여러개일 수도 있는 경우
2️⃣ 자동으로 보여주는 property로 인해 오류를 줄이고 생산 효율을 높이고 싶을 때

🤔 정적검사란?
-> javascript code로 compile하기 이전에 코딩을 하는 순간순간에 검사해주는 검사를 뜻한다. 이는 typescript의 가장 큰 목적이라고 생각된다!!(주관적인 견해!!)

이론적으로는 이해가 갈 듯 안 갈 듯해 역시 예시가 필요하다. 아래 예시를 살펴보자

Example of needing Type Narrowing

function multiple(value: string | number) {
  return value * 2;
}

위의 코드를 보면 value값을 두 가지로 추론할 수 있으며 value로 들어간 값이 1이라고 가정해보자.

  1. value가 string type일 때: string type인 '1'이 인자로 들어가 string type인 '11'을 return
  2. value가 number type일 때: number type인 1이 인자로 들어가 number type인 2를 return

하지만, 이건 우리의 관점이고 typescript입장에서는 string 또는 number type이기 때문에 2를 곱하는 연산을 했을 때 아래와 같이 불만을 토로한다.

이러한 이유로 인해 Type Narrowing이 필요한 예시의 일부분이다.

🍀 typeof guard

위의 예시에서 string type이면 문자열을 2개로 만들고 number type이라면 2를 곱하는 함수를 정의한다고 하자
함수의 인자로 string또는 number type이 들어가는데 type에 따라 사용할 수 있는 property는 다르다. 따라서, type을 1개로 제한 해줘야 하는데 이는 typeof guard로 type을 narrowing할 수 있다.

typeof guard example

function multiple(value: string | number) {
  if (typeof value === 'string') return value.repeat(2)  // 1️⃣
  return value * 2;   // 2️⃣
}

1️⃣에서 type을 string type으로 좁혀주어 value의 type은 string일 수 밖에 없는 환경을 만들어 주었다. 따라서 typescript String 객체의 property를 아래와 같이 빨간색 박스에 관련된 properties를 보여주게 된다.

또한 typescript가 value의 type이 string임을 확신한다.

이렇게 하면 나도 happy하고 typescript도 happy한 불평이 없는 상황이 된다^^

2️⃣ 또한 typescript는 value를 number type으로 인식하고 자동으로 property를 보여주면 그에 맞는 연산을 하기 때문에 불평이없다.

💡 typeof guard의 예시와 같이 type을 1개(또는 typescript가 예측가능한 범위로 좀혀주는 경우...?)로 제한하여 typescript의 기능을 하게끔 하는 것이 type narrowing의 궁극적인 목적같다.

🍀 Truthiness Guard

ts docs에서는 사전에 정의된 단어가 아니라고 한다. 하지만, 네이버에서는 truthiness는 사전적으로 믿고 싶은 것을 믿는 심리라고 한다. 난 이것을 보고 없는 값을 있다고 믿는 심리처럼 해석했으며, 값의 존재 유무에 대해서 확실하게 하는 느낌???을 받았다. 느낀 것이 대충 맞는 것 같지만 주관적이므로....
이것에 대한 예시로는 빈 배열에서 어떠한 정보를 출력할 때 배열이 비어있다면???이라는 예시가 truthiness guard와 적절한 예시가 아닐까 싶다. 왜냐하면 빈 배열은 js에서 truthy로 판명하기 때문이다.

여튼, truthiness guard가 필요한 예시를 아래와 같이 만들었다.

example of needing truthiness guard

function printClients(clients: string[]){
  if (clients.length > 0){   // 1️⃣
    clients.forEach(client => console.log(client));
  }else {
    console.log(`There is no client to show`);
  }
}

const clients: string[] = [];
printClients(clients);

1️⃣ 위치의 코드에서처럼 clients라는 배열 안에 client가 있다는 것을 확실히 해주어 정보가 있을 때만 적절한 로직을 취할 수 있다.

그런데 생각해보니까 undefined가 들어올 때가 더 적절한 예시인 것 같다.
아래와 같이 단순히 두 숫자를 출력해보는 함수가 있다.

하지만, num2는 optional property로 작성되어있어 undefined가 들어 수 있다.

example of other truthiness guard

function printTwoNumbers(num1: number, num2?: number){
  console.log(num1);
  console.log(num2);
}

printTwoNumbers(1);

위와 같이 2개의 인자를 받는 함수에 1개의 값만 집어넣어준다면 num2는 undefined가 되어버린다. num2는 있다고 믿는 심리(truthiness)이지 않을까 싶다.

🍀 Equality Narrowing

equality narrowing은 어떤한 연산을 하기 전에 두 값이 같은 지(with strict equality)를 통해서 비교한다.
여기서 그냥 흔하게 대표적으로 string과 number를 더하는 예시가 떠올랐다.
사용자가 커피 두 잔을 산다고 가정하고 들어오는 가격이 string 또는 number일 수 있고 한 잔만 살 수도 있다고 하자.

example of equality narrowing

function getTwoCoffeesPrice(coffee1: string | number, coffee2: string | number | null){
    if (coffee1 === coffee2){
        return +coffee1 + +coffee2;
    }

    return coffee1;
}

이렇게 strict equlity를 이용하여 두 값이 string 또는 number일 때 if 분기에 걸려 알맞은 연산을 수행하고 커피 한 개를 사지 않으면 하나의 가격만 return하는 함수를 작성할 수 있다.

🍀 In operator Narrowing

객체 안에 어떤 특정한 property가 있는지 없는지 in keyowrd를 이용하여 검사할 수 있다. 이를 이용해서 union type 객체가 들어올 때 적적할 property가 들어왔을 때 원하는 행동을 취할 수 있다.
예를 들면, 사람마다 mbti는 다르다. E라면 외향적일 것이고 'I'라면 내향적일 것이다. 이 때 다양한 사람들(객체)이 들어오는데 어떠한 성격(property) 성격에 따라 적절한 액션을 하게 할 수 있을까?
이에 대한 답이 in operator narrowing이 필요한 상황이다.

example of in operator narrowing

type E = { eWouldSay: () => void };
type I = { iWouldSay: () => void };

function mbtiPersonSay(person: E | I){
    if ('eWouldSay' in person){
        person.eWouldSay()
    }else{
        person.iWouldSay()
    }
}

const ePerson = {
    eWouldSay: () =>  console.log(`I'm outdoor person. let's hang out`);
}

const iPerson = {
    iWouldSay: () => console.log(`I'm not outdoor person. don't touch me`)
}

mbtiPersonSay(ePerson);   // `I'm outdoor person. let's hang out`
mbtiPersonSay(iPerson);   // `I'm not outdoor person. don't touch me`

in operator narrowing으로 mbti가 E인 사람은 활동적으로 말하고 mbti가 I사람은 집돌이 처럼 말할 수 있게 할 수 있다.

🍀 instanceof Narrowing

instaceof는 주어진 값이 확인하고 싶은 객체의 프로토타입 체인 내에 존재하는지 확인하는 연산자이다. 즉, 특정 클래스의 인스턴스에 값이 존재하는지 확인할 수 있다.

typeof가 있는데 instanceof를 왜 쓸까 궁금해할 수도 있다.🤔 이에 대한 답은 primitive value(원시 값)typeof로 type을 비교할 수 있지만 reference value(참조 값)들은 typeof를 이용하면 죄다 'object'로만 나온다. (javascript는 웬만한 것을 객체로 취급하기 때문). 하지만, 우리가 원하는 것은 array인지 date인지 확인하고 싶은데 typeof를 계속 'object'라고 뱉어버리니... instanceof를 사용할 수 밖에 없.

example of instanceof narrowing

function showDate(date: Date | string){
    if (date instanceof Date){
        console.log(date.toLocaleString())
        return ;
    }

    console.log(date);
}

const dateString = '2022.12.17';
const dateObj = new Date();

showDate(dateString);
showDate(dateObj);

🎉 마치며

typescript가 type을 검사하고 자동완성 기능을 제공함으로써 typescript가 익숙한 개발자는 높은 생산성을 가지겠지만, 이제 막 배우는 입장에서는 배울거리가 한 개가 늘어난 것이다.
요새 공부하면서 이것도 배워야 하나??? 저것도 배워야 하나??? 생각이든다....
뭐 결국 스스로 내린 결론은 내가 가는 길에 있어 필요한 걸 그 때 그 때 배우자이다. 작은 일부터 끝내야 큰 일을 도모할 수 있으니 이것저것 배워 얕은 지식을 가져갈 바엔 차근히 끝내는게 나의 생각이다.
typescript를 이용하여 질 좋고 잠재적인 오류를 차단하는 코드를 짜고 싶은 나에게는 typescript는 필수로 생각된다.
이번에는 타입을 좁히는 방법에 대해서 알아봤는데 사실 자동완성 기능을 사용하고 싶어서 배웠다ㅋㅋㅋㅋ
하지만, 배우다 보니까 꼭 자동완성 기능 뿐만 아니라 타입에 잠재적인 오류 또한 차단하고 원하는 로직을 짤 수 있는 스킬을 배운 셈이다.
이렇게 공부하고 나니 문득 내가 정의한 타입에 대해서는 어떻게 type검사를 하지???🤔라는 의문이 생겨서 how to type narrowing with custom type in typescript로 구글링을 하니 여기서 찾아볼 수 있었다. 이것에 대한 정리는 다음 포스트에 이어서 할 생각이다.

📚 참고

type narrowing by typescript docs

profile
step by step

0개의 댓글