[TS] Chapter 6. Typescript 고급 타입

변진상·2023년 6월 6일
0

Typescript 학습

목록 보기
10/13

목표: typescript의 고급타입에 대해 공부한다.

  1. Intersection Types
  2. Type Guards
  3. Discriminated Unions
  4. Type Casting
  5. Function Overloads

intersection type

object의 구조를 정의하는 interface와 type 키워드의 intersection type

object의 구조를 정의하는 interface와 type의 경우 논리합, 논리곱과 같이 동작한다.
// type
type Admin = {
  name: string;
  privileges: string[];
};

type Employee = {
  name: string;
  startDate: Date;
};

type tmp = Admin & Employee;
type tmp = Admin | Employee;

// interface
interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: number;
}

type ElevatedEmployee = Admin & Employee;
// or
// interface ElevatedEmployee extends Admin, Employee {}

type ElevatedEmployee = Admin | Employee;

이 경우 A, B, C에 해당하는 모든 type이 객체 초기화, 할당시 구현이 되어야한다.

이 경우에는 A,B 또는 B,C 또는 A,B,C 모든 타입들이 객체의 초기화, 할당시 구현되어야한다.

union type에서의 intersection type

반면 union 타입의 경우 intersection시 아래 코드와 같다. 이 경우 타입을 담을 수 있는 능력을 논리합, 논리곱 한다고 생각하면 이해가 빠르다.
type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric; 
//=> type Universal = number
type Universal2 = Combinable | Numeric; 
//=> type Universal2 = string | number | boolean

Type Guard

타입가드는 유니언 타입을 돕는다. 유니언을 통해 유연성을 얻는 것은 좋지만, 런타임 시 정확히 어떤 타입을 얻게 될지 알아야하는 경우가 많기 때문이다. type guard의 종류가 몇가지 있는데 이를 아래에서 소개하도록 하겠다.

1. type guard: typeof를 이용한 type 체크

아래의 코드에서 만약 하나의 인자라도 string타입이 들어오게 되면 작동할 로직에 대해 정하는 조건문이 있는데 이 조건문의 조건부가 type guard이다.

type Uniersal = number | string;

function add(a: Uniersal, b: Uniersal) {
  if (typeof a === "string" || typeof b === "string") {
    // 조건문 부분이 type guard
    return a.toString() + b.toString();
  }

  return a + b;
}

2. type guard: javascript in 키워드를 이용한 type check

우리가 커스텀 타입으로 지정한 이름은 JS의 type 키워드로 검사할 수 없다. 그래서 커스텀 타입의 구성요소(propety, method)의 존재 유무를 if문을 통해 체크하거나 in keyword를 이용한 타입가드를 사용할 수 있다.

첫번째 방법의 경우 TS에서 커스텀 탑입으로 정의한 객체의 구성에 접근하지 못해 에러를 띄운다.

아래의 경우 JS의 in keyword를 이용해 각 method나 property가 존재하는지 여부를 따져 실행한다.

function printEmpInfo(e: emp) {
  if ("privileges" in e) {
    console.log("privileges: " + e.privileges);
  }
  if ("startDate" in e) {
    console.log("startDate: " + e.startDate);
  }
}

3. type guard: instanceof

instanceof를 이용해 어떤 class의 instance인지 여부를 체크하는 타입가드도 있다. 아래와 같이 if문에서 체크한다.

중요! instanceof 키워드의 경우 JS의 문법이기 때문에 TS의 문법인 interface를 체크하지 못하니 이를 유의해야한다.

class Car {
  driving() {
    console.log("driving car...!");
  }
}

class Truck {
  driving() {
    console.log("driving Truck Yeah...!");
  }

  load() {
    console.log("loading stuff!");
  }
}

type Vehicle = Car | Truck;

function carAction(v: Vehicle) {
  v.driving();
  if (v instanceof Truck) {
    v.load();
  }
}

const trk = new Truck();

carAction(trk);

discriminated union

유니언 타입의 타입가드를 도와주는 discriminated union이 있다. 유니언 타입의 타입가드를 쉽게 구현할 수 있게 해주는 패턴이다. 객체 타입에도 사용 가능하다. 유니언 타입이 많아지는 경우 타입가드를 위한 if문이 그만큼 많아지는데 이를 작성함에 따라 오타나 타입의 개수만큼 if문이 많아지는 비효율이 발생한다. discriminated union은 이를 해결할 수 있는 패턴이다.

인터페이스와 클래스에 사용가능하다. 개요는 클래스와 인터페이스 선언 당시 서로를 구분하는 keyword나 type을 리터럴로 주어 각 클래스를 switch문을 통해 구분할 수 있는 정보를 남기는 것이다.

// discriminated union

interface Bird {
  type: "bird";
  flyingSpeed: number;
}

interface Horse {
  type: "horse";
  runningSpeed: number;
}

type Animal = Bird | Horse;

function printAnimalSpeed(animal: Animal) {
  let speed;
  switch (animal.type) {
    case "bird":
      speed = animal.flyingSpeed;
      break;
    case "horse":
      speed = animal.runningSpeed;
      break;
  }
  console.log("This animal's speed is " + speed);
}

const nonnie: Bird = { type: "bird", flyingSpeed: 1000000 };

printAnimalSpeed(nonnie);

형변환

// 아래 두 형변환(typecasting)은 동등(equivalent)하다.
const btnById1 = <HTMLButtonElement>document.getElementById("button-yo")!;
//react에서는 컴포넌트 표기법과 충돌할 여지가 있다.
const btnById2 = document.getElementById("button-yo")! as HTMLButtonElement;

typecasting은 타입스크립트가 직접 감지하지 못하는 특접 타입의 값을 타입스크립트에게 알려주는 역할을 함. 예를 들어 DOM에 접근하는 것이다. tag를 통해 DOM element에 접근할 시 그 element가 어떤 종류인지 추론이 가능하지만, id를 통해 JS에서 HTML DOM에 접근하면 TS에서 어떤 tag인지는 판별하지 못한다. 동시에 IDE에서 method나 property에 대한 추천도 하지 못하게 된다. 이런 문제들을 위의 타입캐스팅이 해결해준다.

const btnByTag = document.querySelector("button");
// const btnByTag: HTMLButtonElement | null
// => HTMLButtonElement

const btnById = document.getElementById("button-yo");
// const btnById: HTMLElement | null
// => HTMLElement, element의 종류는 추론 불가

index type

class나 interface 내에 앞으로 만들어질 property의 타입을 제한할 수 있다.

// index type

interface ErrorContainer {
  [prop: string]: string;

  //id: number; => !!! ERROR !!!
}

function overloading

함수 오버로딩은 함수 선언시 같은 이름을 가진 함수를 parameter에 대한 정의를 다르게 해 함수 호출시 입력된 인자의 type이나 개수에 따라 다른 기능을 하도록 할 수 있게하는 기능이다.

// function overloading

type Combinable = number | string;

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

  return a + b;
}
profile
자신을 개발하는 개발자!

0개의 댓글