Type Guard
란 변수의 타입을 좁혀나가는 역할을 하는 특별한 코드 패턴이다.
이를 통해 코드에서 타입 안전성을 확보하고 다양한 상황에 따라 정확한 타입을 사용할 수 있다.
일반적으로 Union Type
을 다룰 때 많이 사용된다.
예를 들어 string | number
와 같은 유니온 타입이 있는 경우, 특정 조건을 충족할 때마다 해당 변수의 타입을 더 좁혀서 더 구체적인 타입을 사용할 수 있다.
구체적인 내용은 밑에서 더 다루기로 하고 일단 유니온 타입이란 무엇일까 ??
유니온 타입이란 OR 연산자를 생각하면 편하다.
function sample(val: string | number) {
...
}
이렇게 함으로써 val
이라는 인자값에는 string과 number 둘다 올 수 잇게 된다.
이 때, 유니온 타입과 같이 자주 언급되는 게 있는데 바로 인터셉션 타입(Intersection Type)
이다.
이것도 비슷하게 AND 연산자를 생각하면 편하다.
// 예시 타입 A
type Car = {
brand: string;
model: string;
year: number;
};
// 예시 타입 B
type ElectricCar = {
batteryType: string;
range: number;
};
// Intersection 타입
type HybridCar = Car & ElectricCar;
// HybridCar를 사용한 객체
const myHybridCar: HybridCar = {
brand: "Toyota",
model: "Prius",
year: 2022,
batteryType: "Lithium-ion",
range: 50,
};
console.log(myHybridCar);
앞서 말했듯이 Union Type
은 두 개 이상의 타입을 허용하는 새로운 타입을 만들 때 사용된다.
이 때 주의할 점이 몇 가지 있다.
코드에서 Union Type
을 검사할 때, 먼저 나오는 타입이 먼저 검사되므로 순서에 유의해야 한다.
type MyType = number | string;
function printType(input: MyType) {
console.log(input.length); // 오류! number 타입에는 length 프로퍼티가 없음
}
Union Type에 null 또는 undefined를 포함시키면 해당 값이 있는지 여부를 항상 검사해야 한다.
type MyType = string | null;
function printLength(input: MyType) {
// 오류! null에는 length 프로퍼티가 없음
console.log(input.length);
}
두 객체의 유니온 타입을 받는 함수에서는 모든 멤버에 공통적을 존재하는 속성만 접근할 수 있다.
type Cat = { kind: 'cat'; purrs: boolean };
type Dog = { kind: 'dog'; barks: boolean };
function doSomething(pet: Cat | Dog) {
// 오류! purrs나 barks 중 어느 것도 접근할 수 없음
console.log(pet.purrs);
}
이 문제를 해결하기 위해서는 타입 가드(Type Guard)를 활용해야 한다 !
위에서 언급했듯이 Type Guard
란 변수의 타입을 좁혀나가는 역할을 하는 특별한 코드 패턴이다.
가장 흔한 Type Guard는 typeof
, instanceof
, in
등의 연산자를 사용하는 것이다.
function printLength(value: string | number) {
if (typeof value === "string") {
// value는 여기서 string 타입으로 좁혀짐
console.log(value.length);
} else {
// value는 여기서 number 타입으로 좁혀짐
console.log("스트링 아님 !!");
}
}
function printProperty(obj: string | number | boolean) {
if (typeof obj === "object" && obj !== null) {
// obj는 여기서 객체 타입으로 좁혀짐
console.log("Object property:", Object.keys(obj));
} else {
console.log("객체 아님 !!");
}
}
주로 객체가 특정 클래스 또는 생성자 함수의 인스턴스인지를 확인할 때 사용된다.
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
// animal은 Dog 클래스의 인스턴스
animal.bark();
} else if (animal instanceof Cat) {
// animal은 Cat 클래스의 인스턴스
animal.meow();
}
}
instanceof
를 사용하여 animal이 Dog 클래스 또는 Cat 클래스의 인스턴스인지를 확인한다. 이를 통해 해당 클래스에 정의된 메서드를 호출할 수 있다.
단, 주의할 점이 있다.
instanceof는 null이나 undefined에 대해서는 항상 false를 반환한다.
in
연산자는 객체의 속성 존재 여부를 확인하고, 해당 속성이 존재하는 경우에만 특정 타입을 추론할 수 있다.
interface Car {
brand: string;
model: string;
}
function printCarInfo(vehicle: Car | { color: string }) {
if ('brand' in vehicle) {
// vehicle은 Car 타입으로 추론됨
console.log(vehicle.brand, vehicle.model);
} else {
// vehicle은 { color: string } 타입으로 추론됨
console.log(vehicle.color);
}
}
이제 위의 doSomething
함수를 Type Guard로 수정해보자 !
type Cat = { kind: 'cat'; purrs: boolean };
type Dog = { kind: 'dog'; barks: boolean };
function doSomething(pet: Cat | Dog) {
// 오류! purrs나 barks 중 어느 것도 접근할 수 없음
console.log(pet.purrs);
}
function doSomething(pet: Cat | Dog) {
if ('purrs' in pet) {
console.log(pet.purrs); // OK
} else {
console.log(pet.barks); // OK
}
}
if 문을 활용해서 타입스크립트 컴파일 에러에서 벗어날 수 있다.
in
의 예제는 다음과 같다.
interface Bird {
fly(): void;
}
interface Fish {
swim(): void;
}
function isFish(pet: Bird | Fish): pet is Fish {
return 'swim' in pet;
}
function move(pet: Bird | Fish) {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}
유니온 타입을 쓸 때 타입스크립트 컴파일 에러가 나오는 경우가 왕왕 있다.
이 때는 타입 가드를 활용해 보도록 하자 !
끝 .. !!