타입 가드는 조건문 안에서 타입 범위를 한정시켜줄 수 있는 방법입니다.
이 글을 통해 타입 가드의 등장 배경, 개념, 사용 방법에 대해 알아보겠습니다.
등장 배경
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
const introduce = (): Developer | Person => {
return { name: 'Kim', age: 20, skill: 'React' };
};
let kim = introduce();
console.log(kim.skill); // Error
유니온 타입은 공통된 속성(name 프로퍼티)에만 접근할 수 있습니다.
그래서 skill 속성에 접근하면 없는 프로퍼티로 간주가 됩니다.
💡 타입 단언 사용 시 문제점
if ((kim as Developer).skill) {
let skill = (kim as Developer).skill;
console.log(skill);
} else if ((kim as Person).age) {
let age = (kim as Person).age;
console.log(age);
}
타입 단언을 통해 skill 속성에 접근할 수 있도록 만들 수 있습니다.
하지만 조건문 내에서 타입 단언이 반복되며 비효율적인 코드가 됩니다.
이때 타입 가드를 통해 조건문 내에서 타입을 손쉽게 한정할 수 있습니다.
타입 가드 (Type Guard)
타입 가드는 조건문 안에서 타입 범위를 한정시켜줄 수 있는 방법입니다.
타입 가드 방법
1. typeof
원시 타입은 JavaScript의 typeof 연산자를 통해 간단하게 타입 가드를 할 수 있습니다.
하지만 커스텀 타입, 인터페이스 등의 복잡한 타입에는 typeof를 사용할 수 없습니다.
const func = (x: number | string) => {
if (typeof x === 'string') {
console.log(x.subtr(1)); // type x : string
console.log(x.substr(1)); // type x : string
}
x.substr(1); // type x : number | string
};
2. instanceof
JavaScript의 instanceof 연산을 통해 객체가 특정한 클래스에 속하는지 확인할 수 있습니다.
class Foo {
common = '123';
foo = 123;
}
class Bar {
common = '123';
bar = 123;
}
const func = (arg: Foo | Bar) => {
if (arg instanceof Foo) {
console.log(arg.foo);
console.log(arg.bar); // Error
}
if (arg instanceof Bar) {
console.log(arg.foo); // Error
console.log(arg.bar);
}
console.log(arg.common);
console.log(arg.foo); // Error
console.log(arg.bar); // Error
};
func(new Foo());
func(new Bar());
🔎 ESLint/max-classes-per-file
ESLint는 한 파일 안에 여러 클래스를 두는 것을 지양합니다.
이는 탐색이 어렵고 잘못된 구조의 코드를 만들 수 있기 때문입니다.
먼저 한 파일 당 하나의 클래스에 대해 단일 책임을 지도록 리팩토링을 해봅시다.
3. in
객체 내부에 특정 프로퍼티가 존재하는지 확인하는 연산자입니다.
interface Human {
think: () => void;
}
interface Dog {
tail: string;
bark: () => void;
}
const func = (x: Human | Dog) => {
if ('tail' in x) {
x.bark(); // type x : Dog
} else {
x.think(); // type x : Human
}
};
4. 리터럴 타입 가드
리터럴 타입은 ===
, ==
, !==
, !=
연산자 또는 switch를 통해 타입 가드를 할 수 있습니다.
특히 요소가 많아질수록 switch의 가독성과 성능이 더 좋습니다.
- 리터럴 타입 : 특정 타입에 속하는 구체적인 값들 / ex) 문자열 리터럴 타입: 'cat', 'dog'..
type States = 'pending' | 'fulfilled' | 'rejected';
// 방법 1. 동등, 일치 연산자
const func = (state: States) => {
if (state === 'pending') {
pendingFunc(); // type state : 'pending'
} else if (state === 'fulfilled') {
fulfilledFunc(); // type state : 'fulfilled'
} else {
rejectedFunc(); // type state : 'rejected'
}
};
// 방법 2. switch
const func = (state: States) => {
switch (state) {
case 'pending':
pendingFunc(); // type state : 'pending'
break;
case 'fulfilled':
fulfilledFunc(); // type state : 'fulfilled'
break;
case 'rejected':
rejectedFunc(); // type state : 'rejected'
break;
}
};
5. 사용자 정의 타입 가드
커스텀 타입, 인터페이스 등의 복잡한 타입은 typeof, instanceof 등을 활용하기 어렵습니다.
또한 타입 판단 로직을 재사용하고 싶을 때가 생길 수 있습니다.
이때 사용자 정의 타입 가드를 사용하면 좋습니다.
const isDeveloper = (target: Developer | Person): target is Developer => {
return (target as Developer).skill !== undefined;
};
if (isDeveloper(kim)) {
kim.skill; // type kim : Developer
} else {
kim.age; // type kim : Person
}
사용자 정의 타입 가드는 주로 isTypeName 형태의 함수명을 많이 사용합니다.
parameterName is Type
의 의미는 매개변수가 해당 타입인지 구분하는 키워드로 해석됩니다.
리턴 값은 target 객체에 skill 메서드가 있다면 Developer 타입이 맞다는 의미의 불리언 값입니다.
즉 isDeveloper 함수를 통과하고 나면 전달받은 인자가 Developer인지 아닌지를 리턴해줍니다.
'프론트엔드 > TypeScript' 카테고리의 다른 글
[TypeScript] 유틸리티 타입 구현하기 (Utility Types) (0) | 2022.06.07 |
---|---|
[TypeScript] 제네릭 (Generic) (0) | 2022.06.07 |
[TypeScript] Interface와 Type Alias의 차이점 (0) | 2022.06.06 |
[TypeScript] 타입 단언 (Type Assertion) (0) | 2022.06.02 |
[TypeScript] 타입 종류 (0) | 2022.05.31 |