[Typescript] Union Type을 쓸 때 Type Guard를 활용하여 단점 극복하기

in-ch·2023년 12월 17일
3

typescript

목록 보기
4/4
post-thumbnail

서론


Type Guard란 변수의 타입을 좁혀나가는 역할을 하는 특별한 코드 패턴이다.
이를 통해 코드에서 타입 안전성을 확보하고 다양한 상황에 따라 정확한 타입을 사용할 수 있다.

일반적으로 Union Type을 다룰 때 많이 사용된다.
예를 들어 string | number와 같은 유니온 타입이 있는 경우, 특정 조건을 충족할 때마다 해당 변수의 타입을 더 좁혀서 더 구체적인 타입을 사용할 수 있다.

구체적인 내용은 밑에서 더 다루기로 하고 일단 유니온 타입이란 무엇일까 ??

유니온 타입 (Union Type)

유니온 타입이란 OR 연산자를 생각하면 편하다.

  • 예시 코드
function sample(val: string | number) {
	...
}

이렇게 함으로써 val이라는 인자값에는 string과 number 둘다 올 수 잇게 된다.

이 때, 유니온 타입과 같이 자주 언급되는 게 있는데 바로 인터셉션 타입(Intersection Type)이다.

인터셉션 타입 (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은 두 개 이상의 타입을 허용하는 새로운 타입을 만들 때 사용된다.

이 때 주의할 점이 몇 가지 있다.

1. 타입의 순서와 영향

코드에서 Union Type을 검사할 때, 먼저 나오는 타입이 먼저 검사되므로 순서에 유의해야 한다.

type MyType = number | string;

function printType(input: MyType) {
  console.log(input.length); // 오류! number 타입에는 length 프로퍼티가 없음
}

2. Null 및 Undefined 고려

Union Type에 null 또는 undefined를 포함시키면 해당 값이 있는지 여부를 항상 검사해야 한다.

type MyType = string | null;

function printLength(input: MyType) {
  // 오류! null에는 length 프로퍼티가 없음
  console.log(input.length);
}

3. 객체의 타입 추론

두 객체의 유니온 타입을 받는 함수에서는 모든 멤버에 공통적을 존재하는 속성만 접근할 수 있다.

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란 변수의 타입을 좁혀나가는 역할을 하는 특별한 코드 패턴이다.

가장 흔한 Type Guard는 typeof, instanceof, in 등의 연산자를 사용하는 것이다.

typeof

  • 대표 예시
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("객체 아님 !!");
  }
}

instanceOf

주로 객체가 특정 클래스 또는 생성자 함수의 인스턴스인지를 확인할 때 사용된다.

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

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);
  }
}

Type Guard로 Union Type 극복하기


이제 위의 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);
}
  • TypeGuard 활용
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();
  }
}

결론


유니온 타입을 쓸 때 타입스크립트 컴파일 에러가 나오는 경우가 왕왕 있다.
이 때는 타입 가드를 활용해 보도록 하자 !

끝 .. !!

profile
인치

0개의 댓글