타입 가드란 컴파일러가 타입을 예측할 수 있도록 조건문을 통해 타입을 좁혀나가는 기법으로 에러를 방지하고 코드 안정성을 향상시킬 수 있다.
union 타입을 사용할 경우 타입 가드를 통해 안전성을 유지하는 것이 좋다.
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
타입 단언을 통해 강제적으로 타입을 명시하면 컴파일 단계에서 에러를 없앨 수 있지만 런타임 환경에서 에러를 발생하기 때문에 사용을 지양해야한다.
또한 조건문 마다 타입 단언이 반복되어 가독성이 떨어진다.
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 연산자를 사용하면 객체가 특정한 클래스에 속하는지 확인할 수 있다.
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 키워드를 명시하며 타입을 구분하고 컴파일러에게 알린다.
컴파일러가 타입을 판단하는 방법을 직접 정의하거나 타입 판단 로직을 재사용하고자 할 때 사용한다.