[TypeScript] Type Narrowing(타입 좁히기)

김방울·2023년 1월 26일
1

TypeScript

목록 보기
3/12
post-thumbnail

코딩애플님의 '빠르게 마스터하는 타입스크립트' 강의를 수강한 뒤 정리를 위해 덧붙여 작성한 글입니다.

Type Narrowing

여러 경우의 수를 가진, 보다 넓은 타입을 더 좁은(더 적은 경우의 수를 갖는) 타입으로 재정의하는 행위

//js
const getCircleWidth = (radius) => {
  return Math.pow(radius, 2) * Math.PI;
}

//ts
const getCircleWidth = (radius?: number)  : number  => {
  return Math.pow(radius, 2) * Math.PI;
}

일반적인 자바스크립트(바닐라 자바스크립트)는 매개변수가 선언된 함수 호출 시, 인자(파라미터)를 넣어주지 않더라도 컴파일 에러를 띄우지 않습니다.

하지만 타입스크립트는 타입 검사에 엄격하기 때문에 매개변수를 선언하고 사용하지 않으면 '인수가 제공되지 않았다'고 에디터에서 경고를 띄워 줍니다.

위와 같이 매개변수 뒤에 물음표? 를 붙여 주면 매개변수를 선택적으로 사용할 수 있는 상태가 됩니다. 즉, 함수 사용 시 매개변수의 생략이 가능해집니다. (옵셔널 파라미터)

그런데 이게 단순히 생략을 가능하게 처리해주는 문법처럼 보이나, 사실은 radius 변수를 number | undefined 의 유니온 타입으로 만들어 주는.. 축약형 문법입니다.🤔

사실 바닐라 자바스크립트에서도 매개변수를 선언하고 함수 호출 시 사용하지 않으면,undefined 가 매개변수에 할당되게 되므로 호출 시 동일한 결과를 얻을 수 있습니다.

이렇게 옵셔널 파라미터를 사용할 경우 undefined 타입이 들어갈 가능성이 있기 때문에,
타입을 엄격하게 체크하는 타입스크립트의 특성상 Math.pow 의 매개변수로 undefined가 들어갈 수 없다는 에러를 에디터에서 띄워 주게 됩니다. (radius의 붉은 밑줄)

이렇게 타입이 확실하지 않을 때 생길 수 있는 부작용들을 막기 위해 Type Narrowing 기법을 사용합니다.

interface Person {
  // favoriteLanguage 속성은 string|undefined 타입
  favoriteLanguage?: string;
}

function isFavoriteLangScript(p: Person): boolean {
  // 매개변수 p가 undefined 타입일 경우
  if (p.favoriteLanguage === undefined) {
    return false;
  }
  
  // 매개변수 p가 string 타입일 경우
  const lowerCased = p.favoriteLanguage.toLowerCase();
  return lowerCased.includes('script');
}

위의 예제는 if문을 통해 string | undefined 라는 넓은 타입에서 string 이라는 좁은 타입으로 타입을 좁혀 줍니다.
당연하지만 꼭 if문만 사용 가능한 것이 아니라, switch-case문 등 타입의 조건을 체크할 수 있는 제어문이라면 어느 것을 사용해도 무방합니다.

이런 식으로 조건문 등을 사용하여 컴파일러가 타입을 예측할 수 있도록 타입을 좁혀 주는 방식을 Type Narrowing 기법이라고 합니다.

typeof 연산자

const getWelcomeMsg = (userName?: string) => {
  if (typeof userName === 'undefined'){
    return '로그인이 필요합니다.';
  }
  return `안녕하세요, ${userName} 님!`;
}

자바스크립트에서 기본적으로 제공하는 typeof 연산자를 이용하여 타입을 쉽게 체크할 수 있습니다. (typeof 연산자는 문자열을 반환합니다.)

다만 이 경우 typeof null 을 사용하였을 때는 반환값이 object 가 되므로, null 값을 체크할 때는

if (userName === null)

다음과 같이 체크하는 것이 좋습니다. (strictNullChecks) 😀

in 연산자

interface Bird {
  head: 1;
  body: 1;
  wing: 2;
  leg: 2;
}
interface Person {
  head: 1;
  body: 1;
  leg: 2,
  arm: 2,
}

const getIsAnimal = (target: Cat | Bird | Person) => {
  if ('tail' in target) {
    // Cat | Bird | Person
    return true;
  }
  else if ('wing' in target){
    // Bird | Person
    return true;
  }
  else{
    // Person
    return false;
  }
}

속성 in 객체명 구문으로, 해당 속성이 객체에 존재하면 true 를 반환해 주는 in 연산자로도 타입 체크가 가능합니다. 역시 자바스크립트 기본 제공 문법입니다.

instanceof 연산자

class Cat {
  name = '빵울';
  meow (){
    console.log('애옹?');
  }
}

class Person {
  name = '빵울이집사'
  say(){
    console.log('따뜻한 아메리카노 주세요');
  }
}

function getAct(target: Cat | Person){
  if (target instanceof Cat){
    // Cat | Person
    target.meow();
    return;
  }
  // Person
  target.say();
  return;
}

위의 in 연산자와 비슷하게, 객체가 특정 클래스에 속하는지 아닌지를 비교할 수 있는 instanceof 연산자를 통해 타입을 좁힐 수 있습니다.

obj instanceof Class

객체가 특정 클래스를 상속받았다면 true 를 반환합니다.

클래스 생성자 Cat을 호출하여 bbangEul 객체를 만들어 주었습니다. 매개변수 target 에는 Cat 타입이, Person 타입이 들어갈 수도 있는데 Person 클래스에 없는 meow() 메소드를 호출하려 하니 에러가 발생합니다.

다음과 같이 분기처리를 해 주며 타입을 좁혀 주면 에러가 발생하지 않습니다. 🙂

etc.

이 외에도 리터럴하게 타입을 체크하는 방법, 사용자 정의 타입 체크 등 여러 방식이 있습니다. 어떤 연산자를 사용하느냐, 어떤 제어문을 사용하느냐보다는 타입의 범위를 좁혀 나가면서 방어적으로 코딩하는 것 이 핵심인 것 같습니다. 👾

참고자료

profile
코딩하는 고양이🐱 / UI Developer, Front-end Developer

0개의 댓글