TypeScript - 타입 가드

uk·2023년 7월 19일
0

TypeScript

목록 보기
5/5

타입 가드

타입 가드란 컴파일러가 타입을 예측할 수 있도록 조건문을 통해 타입을 좁혀나가는 기법으로 에러를 방지하고 코드 안정성을 향상시킬 수 있다.

union 타입을 사용할 경우 타입 가드를 통해 안전성을 유지하는 것이 좋다.


typeof

const testFunc = (a: string | number) => {
  console.log(a.toUpperCase());
};

// Property 'toUpperCase' does not exist on type 'string | number'. 
// Property 'toUpperCase' does not exist on type 'number'.

toUpperCase 메서드는 문자열을 대문자로 변환해주는 문자열 메서드이기 때문에 number 타입에서는 사용할 수 없다.

인자로 넘어온 a는 string 또는 number 타입일 수 있기 때문에 컴파일러는 number 타입이 들어와 에러가 발생하는 것을 방지하기 위해 미리 에러를 발생시킨다.

이때 자바스크립트에서 피연산자의 타입을 문자열로 반환해주는 typeof 연산자를 사용하여 분기하면 위 문제를 해결할 수 있다.

typeof는 원시 타입일 경우 사용하고 type 또는 interface와 같은 복잡한 타입은 사용할 수 없다.

const testFunc = (a: string | number) => {
  if (typeof a === 'string') {
    console.log(a.toUpperCase());
  }
  if (typeof a === 'number') {
    console.log(a.toString());
  }
};

// else 사용
const testFunc = (a: string | number) => {
  if (typeof a === 'string') {
    console.log(a.toUpperCase());
  } else {
    console.log(a.toString());
  }
};

타입 단언 사용

export const testFunc = (a: string | number) => {
  console.log((a as string).toUpperCase());
};

testFunc(1234);  // TypeError: a.toUpperCase is not a function

타입 단언을 통해 강제적으로 타입을 명시하면 컴파일 단계에서 에러를 없앨 수 있지만 런타임 환경에서 에러를 발생하기 때문에 사용을 지양해야한다.

또한 조건문 마다 타입 단언이 반복되어 가독성이 떨어진다.


instanceof

class Person {
  name = 'kim';
  age = 20;
}

class Developer {
  name = 'lee';
  skill = 'ts';
}

export const testFunc = (a: Person | Developer) => {
  if (a instanceof Person) {
    console.log(a.name);
    console.log(a.skill); // Property 'skill' does not exist on type 'Person'.
  }

  if (a instanceof Developer) {
    console.log(a.age); // Property 'age' does not exist on type 'Developer'.
    console.log(a.skill);
  }

  console.log(a.name);
    console.log(a.age); // Property 'age' does not exist on type 'Person | Developer'.
    console.log(a.skill); // Property 'skill' does not exist on type 'Person | Developer'.
};

testFunc(new Person());
testFunc(new Developer());

instanceof 연산자를 사용하면 객체가 특정한 클래스에 속하는지 확인할 수 있다.


in

interface Person {
  name: string;
}

interface Developer {
  name: string;
  age: number;
}

export const testFunc = (a: Person | Developer) => {
  if ('age' in a) {
    console.log(a.name); // a: Developer
  } else {
    console.log(a.name); // a: Person
  }
};

in 연산자를 통해 객체 내부에 특정 property가 존재하는지를 확인할 수 있다.


리터럴 타입 가드

type Language = 'javascript' | 'java' | 'python';

export const testFunc = (a: Language) => {
  if (a === 'javascript') {
    // a: javascript
  } else if (a === 'java') {
    // a: java
  } else {
    // a: python
  }
}


const testFunc = (a: Language) => {
  switch (a) {
    case 'javascript':
      // a: javascript
      break;
    case 'java':
      // a: java
      break;
    case 'python':
      // a: python
      break;
  }
};

리터럴 타입은 ===, ==, !==, != 연산자를 통해 타입을 구분할 수 있고 원시 타입을 typeof를 사용해서 타입 가드할 때와 마찬가지로 switch 문을 통한 타입 가드가 가능하다.

코드가 길어질 수록 switch 문을 사용하는 것이 가독성이 좋다.


배열 타입 가드

function testFunc(a: string | string[]) {
  if (Array.isArray(a)) {
	// a: string[]
  } else {
    // a: string
  }
}

Array.isArray() 메서드를 통해 배열인지 확인이 가능하다.


사용자 정의 타입 가드

interface Person {
  name: string;
}

interface Developer {
  name: string;
  age: number;
}

// 커스텀 타입 가드 함수, 조건문에서 사용되며 타입을 구분한다.
// a 객체에 age 속성이 있으면 Developer 타입이며 전달 인자가 Developer 타입인지 아닌지를 boolrean 형태로 반환
const isDeveloper = (a: Person | Developer): a is Developer => {
  return (a as Developer).age !== undefined;
};

export const testFunc = (a: Person | Developer) => {
  if (isDeveloper(a)) {
    // a: Developer
  } else {
    // a: Person
  }
};

간단한 타입은 자바스크립트에서 제공하는 연산자를 통해 분기 처리가 가능하지만 복잡한 타입은 is 키워드를 통해 커스텀 타입 가드 함수를 만들 수 있다.

함수의 리턴 타입에 is 키워드를 명시하며 타입을 구분하고 컴파일러에게 알린다.

컴파일러가 타입을 판단하는 방법을 직접 정의하거나 타입 판단 로직을 재사용하고자 할 때 사용한다.

0개의 댓글