[TypeScript] 고급타입 : TypeGuard

Hoplin·2023년 6월 30일
0
post-thumbnail

TypeScript에는 Union 타입이라는것이 존재한다.(이와 반대로 Intersection 타입도 존재한다)

type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric;

유니온 타입을 사용하면 위와 같이 가능성이 여러개인 타입들을 한번에 묶어서 표현할 수 있다. 하지만 유니온 타입에는 문제점이 있다. 이러한 유연성을 가지는것은 좋지만, TypeScript는 런타임시 유니온 타입의 변수가 어떤 타입을 가져야 하는지를 알 수 있도록 타입의 범위를 줄여주어야한다. 이러한 문제점을 해결해 주는것이 TypeGuard이다. typeof, instanceof연산자를 활용하여 TypeGuard를 할 수 있다.

typeof

분기문 활용

function add(a: Combinable, b: Combinable) {
  return a + b; // '+' 연산자를 'Combinable' 및 'Combinable' 형식에 적용할 수 없습니다.ts(2365)
}

위 코드를 IDE에 입력하면 오류가 나오는것을 알 수 있다.하지만 Combinable이라는 사용자 정의 타입에서는 +연산이 존재하지 않으며 유효하지 않다. Combinable타입에는 numberstring이 있으므로 +연산이 가능한 타입으로 범위를 줄여주어야 한다.

function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

in 활용하기

아래와 같은 세가지 타입이 있다고 가정한다.

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를 활용해보자. 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);
  }
}
profile
더 나은 내일을 위해 오늘의 중괄호를 엽니다

0개의 댓글