TypeScript에는 Union 타입이라는것이 존재한다.(이와 반대로 Intersection 타입도 존재한다)
type Combinable = string | number;
type Numeric = number | boolean;
type Universal = Combinable & Numeric;
유니온 타입을 사용하면 위와 같이 가능성이 여러개인 타입들을 한번에 묶어서 표현할 수 있다. 하지만 유니온 타입에는 문제점이 있다. 이러한 유연성을 가지는것은 좋지만, TypeScript는 런타임시 유니온 타입의 변수가 어떤 타입을 가져야 하는지를 알 수 있도록 타입의 범위를 줄여주어야한다. 이러한 문제점을 해결해 주는것이 TypeGuard
이다. typeof
, instanceof
연산자를 활용하여 TypeGuard를 할 수 있다.
function add(a: Combinable, b: Combinable) {
return a + b; // '+' 연산자를 'Combinable' 및 'Combinable' 형식에 적용할 수 없습니다.ts(2365)
}
위 코드를 IDE에 입력하면 오류가 나오는것을 알 수 있다.하지만 Combinable
이라는 사용자 정의 타입에서는 +
연산이 존재하지 않으며 유효하지 않다. Combinable
타입에는 number
와 string
이 있으므로 +
연산이 가능한 타입으로 범위를 줄여주어야 한다.
function add(a: Combinable, b: Combinable) {
if (typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
return a + b;
}
아래와 같은 세가지 타입이 있다고 가정한다.
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
type UnknownEmployee = Employee | Admin;
그리고 UnknownEmployee
타입을 받아 정보를 출력하는 함수를 작성한다 가정하자.
function printEmployee(emp: UnknownEmployee) {
console.log(emp.name);
console.log(emp.privileges); // 'UnknownEmployee' 형식에 'privileges' 속성이 없습니다.'Employee' 형식에 'privileges' 속성이 없습니다.
}
위와 같이 오류가 나는것을 볼 수 있다. 이 오류가 나는 이유는 UnknownEmployee
타입의 Employee
타입에는 privileges
가 없기 때문이다. 그렇다면, 위에서 봤던 typeof
를 활용하여 TypeGuard를 하면 어떨까?
function printEmployee(emp: UnknownEmployee) {
console.log(emp.name);
if (typeof emp === "Admin") { // '"string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"'이(가) '"Admin"'과(와) 겹치지 않으므로 이 비교는 의도하지 않은 것 같습니다.
console.log(emp.privileges);
}
}
아쉽게도 typeof
연산자 같은 경우 JavaScript의 원시타입들만 판별이 가능하다. 즉 위의 코드와 같이 사용자 정의타입과 typeof
를 같이 쓸 수 없다. 위와 같이 객체타입의 사용자 정의 타입은 in
연산자를 통해 TypeGuard해줄 수 있다.
일반적으로 JavaScript에서는 in
연산자는 명시된 속성이 있으면 true
를 반환하고, 객체타입에서는 key
를 통해 판별한다
const a = {
key1: 10,
key2: 20,
key3: 30,
};
console.log(Object.keys(a)); //[ 'key1', 'key2', 'key3' ]
console.log("key1" in a); // true
console.log("key10" in a); // false
이를 활용해 위 코드에서도 아래와 같이 바꿔주면된다.
function printEmployee(emp: UnknownEmployee) {
console.log(emp.name);
if ("privileges" in emp) {
console.log(emp.privileges);
}
}
이와 동일하게 Employee
타입에만 있는 startDate
도 출력한다고 하면, 아래와 같이 고칠 수 있다.
function printEmployee(emp: UnknownEmployee) {
console.log(emp.name);
if ("privileges" in emp) {
console.log(emp.privileges);
}
if ("startDate" in emp) {
console.log(emp.startDate);
}
}
이번에는 instanceof
를 활용해보자. instanceof
는 주로 클래스에 대한 TypeGuard를 할 때 사용된다. 아래와 같이 2개의 타입과 유니온 타입이 선언되어있다고 가정하자.
class Car {
public drive(): void {
console.log("Driving Car...");
}
}
class Truck {
public drive(): void {
console.log("Driving Truck...");
}
public loadCargo(amount: number): void {
console.log("Loading Cargo");
}
}
type Vehicle = Car | Truck;
그리고 Vehicle
타입 변수를 받아 메소드를 호출하는 함수가 있다고 가정한다
function useVehicle(vehicle: Vehicle) {
vehicle.drive();
vehicle.loadCargo(10);//'Vehicle' 형식에 'loadCargo' 속성이 없습니다.'Car' 형식에 'loadCargo' 속성이 없습니다.ts(2339)
}
loadCargo()
메소드는 Truck
클래스의 인스턴스에만 존재한다. 그렇기에 오류가 나는것이다. 위에서 활용했던 in
연산자를 활용할 수 있다.
function useVehicle(vehicle: Vehicle) {
vehicle.drive();
if ("loadCargo" in vehicle) {
vehicle.loadCargo(10);
}
}
이는 클래스 인스턴스의 prototype을 통해 검사하게 되면 loadCargo
가 key로 존재하기 때문이다.
하지만 이러한 방식은 메소드 이름 혹은 속성의 프로퍼티 key의 이름을 문자열로 하드코딩 해야한다는 단점이 있다. 이러한 문제점을 instanceof
연산자를 통해 더 안전한 코드를 작성할 수 있는것이다.
function useVehicle(vehicle: Vehicle) {
vehicle.drive();
if (vehicle instanceof Truck) {
vehicle.loadCargo(10);
}
}