본문 바로가기
TypeScript

Type guard / narrowing / predicates

by 찬찬2 2022. 9. 13.

타입 가드를 사용하는 이유는 보통 fetch 로 union 타입의 데이터를 가져올때이다.

 

만약 API 호출로 받아온 데이터의 타입이 string인데 toFixed(n) 과 같은 메서드를 사용했을때 에러를 발생시킨다. 당연히 그 뒤에 오는 코드들도 실행되지 않으니 큰 문제가될 수 있다.

 

타입가드, 타입 좁히기할때 사용할 수 있는 방법으로는..

 

■ Primitive value check

- typeof : 원시값(string, number, boolean) 을 체크한다.

 

■ Reference value check

- instanceof : class only

 

■ Object's key name check

- key in obj : 객체에서 key 값이 있는지 확인

 

■ Object array check

- isArray : 배열

 

predicates 에서 predicate 란, 예측하다 또는 단정하다, 단언/선언하다라는 의미이다. 즉, 타입을 단정짓는것이라고 이해하면된다.

 

요즘 트렌드는 조건문 if 에 조건식을 함수로 넣는 것인데...

 

interface Adult {
  type: 'adult';
  age: number;
  drinkAlcohol(): void;
}

interface Minor {
  type: 'minor';
  age: number;
  drinkJuice(): void;
}

function isAdult(person: Adult | Minor): boolean {
  return person.type === 'adult' 
}

function drink(person: Adult | Minor): void {
  if (isAdult(person)) {
    person.drinkAlcohol(); // 타입스크립트가 정확히 추론하지 못해 drinkAlcohol 매서드 부분에 에러가 발생한다.
    return;
  }

  person.drinkJuice(); // 여기도 마찬가지
}

 

함수 isAdult 에서 정확히 구분한뒤 boolean 값으로 만약 'adult' 일 경우 true, 아니면 false 를 반환할 수 있도록 함수의 리턴타입을 boolean 으로 했음에도, drink 함수에서는 정확하게 감지를 못해 객체의 매서드에 접근할 수 없는 것을 볼 수 있다.

 

정답부터 보자면...

 

function isAdult(person: Adult | Minor): person is Adult {
  return person.type === 'adult' 
}

 

이렇게 함수의 리턴타입을 boolean 으로 true 또는 false, 두 개의 선택지를 주는 것이 아니라, 단 하나의 결과만 보여주어 단순화하는 것이 요새 트렌드이다.

 

function isAdult(person: Adult | Minor): person is Adult {
  return person.type === 'adult' 
}
function isMinor(person: Adult | Minor): person is Minor {
  return person.type === 'minor' 
}

function drink(person: Adult | Minor): void {

  if (isAdult(person)) person.drinkAlcohol();
  if (isMinor(person)) person.drinkJuice();

  // 또는

  if (isAdult(person)){
    person.drinkAlcohol();
    return;
  };
  person.drinkJuice();

}

 


 

원시값인 경우: typeof 

 

function numOrStr( a: number | string ){  }

 

위 함수는 매개변수로 숫자 또는 문자를 받을 수 있다.

 

function numOrStr( a: number string ){
    a.toFixed(1)
}

 

numOrStr( "123" ) // error

 

toFixed 매서드는 숫자에 대해서만 사용할 수 있는 매서드인데 만약 매개변수가 문자열일 경우 자바스크립트는 에러 메시지를 발생시킨다. 이러한 다중 데이터 타입을 수용하는 함수의 매개변수인 경우 타입가드(타입구분)를 통해 특정 타입에 대해서만 동작할 수 있도록 코드를 작성해야 한다.

 

function numOrStr( a: number | string ){ // 숫자 또는 문자
    if(typeof a === "number"){
        a.toFixed(1);
    }
    if(typeof a === "string"){
        a.charAt(3);
    }
}

 

numOrStr( "123" )

numOrStr( 123)


 

배열인 경우: isArray

 

function numOrNumArray(a: number | number[ ]){ // 숫자 또는 숫자 배열
    if(Array.isArray(a)){
        a.concat(4);
    }
    if(typeof a === "number"){
        a.toFixed(1);
    }
}

numOrNumArray(123);
numOrNumArray([1, 2, 3]);


 

클래스인 경우: instanceof

 

class A { aaa( ) {  } }

class B { bbb( ) {  } }

 

function aOrB( param: A | B ) { }

 

aOrB(new A)
aOrB(A) // error

 

aOrB 함수의 매개변수 A | B는 class A, class B 자체를 받는 것이 아니라 new A, new B 인스턴스를 받는다.

 (★class는 타입으로 사용할 수 있다.)

 

function aOrB( param: A | B ) {
    if( param instanceof A ){
        param.aaa()
    }
    if( param instanceof B ){
        param.bbb()
    }
}


 

객체의 경우 속성과 속성값을 바탕으로 구분지을 수 있다.

 

type B = { type: "b", bbb: string };
type C = { type: "c", ccc: string };
type D = { type: "d", ddd: string };

 

속성값으로 구분
function typeCheck(a: B | C | D){
    if(a.type === "b"){
        a.bbb;
    }
    if(a.type === "c"){
        a.ccc
    }
    if(a.type === "d"){
        a.ddd
    }
}

 

속성명으로 구분

function typeCheck(a: B | C | D){
    if("bbb" in a){
        a.bbb;
    }
    if("ccc" in a){
        a.ccc;
    }
    if("ddd" in a){
        a.ddd;
    }
}

댓글