글 작성자: 망고좋아
반응형

🎯 타입스크립트 타입 가드(Type Guard)

  • 데이터의 타입을 알 수 없거나, 될 수 있는 타입이 여러 개라고 가정할 때 조건문을 통해 데이터의 타입을 좁혀나가는 것
  • 데이터의 타입에 따라 대응하여 에러를 최소화할 수 있음
  • 타입을 통해 '가드'하는 코드, 방어적인 코드를 짤 수 있음

 

📝 타입 가드를 사용해 유니온 타입 사용

// 구별된 유니온
type Human = {
    think: () => void;
};

type Dog = {
    bark: () => void;
}

declare function getEliceType(): Human | Dog;

const elice =  getEliceType();
  • elice가 Human인지, Dog인지 확신할 수 없는 상태
  • 타입스크립트가 타입을 추론할 수 있도록 단서를 줘보면 어떨까?

 

📕 구별된 유니온

  • 타입을 구별할 수 있는 단서가 있는 유니온 타입
  • 구별된 유니온, 태그 된 유니온, 서로소 유니온이라는 다양한 이름을 가지고 있다.

 

📝 구별된 유니온으로 타입 가드 하는 방법

type Human = {
    think: () => void;
};

type Dog = {
    tail: string; // 구별한 단서(태그)
    bark: () => void;
}

declare function getEliceType(): Human | Dog;

const elice =  getEliceType();

if ('tail' in elice) {
    elice.bark(); // Dog 타입으로 추론할 수 있다.
} else {
    elice.think(); // Human 타입으로 추론할 수 있다.
}
  1. 첫 번째, 각 타입에 타입을 구별한 단서(태그)를 만든다.
  2. 두 번째, 조건문을 통해 각 타입의 단서로 어떤 타입인지 추론한다.
  3. 세 번째, 해당 타입의 프로퍼티를 사용한다.

 

📝 실무에서 자주 쓰는 구별된 유니온과 타입 가드

type SuccessResponse = {
    status: true;
    data: any;
};

type FailureResponse = {
    status: false;
    error: Error;
};

type CustomResponse = SuccessResponse | FailureResponse;

declare function getData(): CustomResponse;

const response: CustomResponse = getData();

if(response.status) {
    console.log(response.data);
} else if (response.status === false) {
    console.log(response.error);
}
  • 서버에서 오는 응답 또는 함수의 결과가 여러 갈래로 나뉠 때 구별된 유니온 사용 가능
  • 타입의 단서를 토대로 타입 가드를 하고, 응답의 결과에 따라 다른 작업을 실행시켜준다.

 

📝 instanceof를 사용한 타입 가드

class Developer {
    developer() {
        console.log("업무 중")
    }
}

class Designer {
    design() {
        console.log("업무 중")
    }
}

const work = (worker: Developer | Designer) => {
    if (worker instanceof Developer) {
        worker.developer();
    } else {
        worker.design();
    }
}
  • TS에서는 클래스도 타입이다.
  • 객체가 어떤 클래스의 객체인지 구별할 때 사용하는 연산자.
  • 인스턴스 intanceof 클래스와 같이 사용

 

📝 typeof를 사용한 타입 가드

const add = (arg?: number) => {
    if (typeof arg === 'undefined') {
        return 0;
    }

    return arg + arg;
}
  • 데이터의 타입을 반환하는 연산자.
  • typeof 데이터 === 'string'과 같이 사용
  • typeof 데이터 === 'undefined' 처럼 undefined 체크도 가능
  • 참고로 데이터 == null과 같이 쓰면 null, undefined 둘 다 체크 ⇒ 일치 연산자(===)가 아닌 동등 연산자(==) 사용
  • null과 undefined는 따로 체크해주는 게 더 명확하다.

 

📝 in을 사용한 타입 가드

type Human = {
    think: () => void;
};

type Dog = {
    tail: string;
    bark: () => void;
}

declare function getEliceType(): Human | Dog;

const elice =  getEliceType();

if ('tail' in elice) {
    elice.bark();
} else {
    elice.think();
}
  • 문자열 A in Object : object의 key 중에 문자열 A가 존재하는지 확인함
    • 문자열 A는 object의 key를 의미한다.

 

📝 literal type guard

type Action = 'click' | 'hover' | 'scroll';

const doPhotoAction = (action: Action) => {
    switch (action) {
        case 'click':
            showPhoto()
            break;
        case 'hover':
            zoomInPhoto()
            break;
        case 'scroll':
            loadPhotoMore()
            break;

    }
}

// or

const doPhotoAction2 = (action: Action) => {
    if(action === 'click') {
        showPhoto();
    } else if (action === 'hover') {
        zoomInPhoto();
    } else if (action === 'scroll') {
        loadPhotoMore();
    }
}
  • 리터럴 타입 : 특정 타입의 하위 타입(구체적인 타입)이다. ex) string 타입의 리터럴 타입: ‘cat’, ‘dog’, …
    • 즉, string 문자열 타입에 속하는 구체적인 값들을 리터럴 타입이라고 부른다.
  • 리터럴 타입은 동등(==), 일치(===) 연산자 또는 switch로 타입 가드 가능
  • 하나의 value 값에 따라 조건문을 작성하는 경우, 대부분의 경우 switch의 연산 결과가 if-else보다 더 빠르므로 되도록 switch를 쓰는 걸 권장
  • 조건문의 개수가 적으면 큰 차이는 없지만, 조건문의 개수가 많아질수록 switch의 성능이 더 빠르다.

 

📝 Type guard에 유용한 오픈소스 - sindresorhus/is

import is from '@sindresorhus/is';

const getString = (arg?: string) => {
    if(is.nullOrUndefined(arg)) {
        return '';
    }

    if(is.emptyStringOrWhitespace(arg)) {
        return '';
    }

    return arg;
}
  • 사용자 정의 함수를 사용해 타입 가드 가능
  • 오픈소스 중 sindresorhus/is를 사용하여 가독성 있게 타입 체크 가능
  • yarn add @sindersorhus/is 또는 npm install @sindersorhus/is
  • https://github.com/sindresorhus/is
반응형