본문 바로가기
Typescript

Typescript | 제네릭 인터페이스, 제네릭 타입 별칭, 제네릭 클래스, Promise와 제네릭

by 리잼 2024. 11. 3.
반응형

제네릭 인터페이스

제네릭은 인터페이스에도 적용할 수 있다. 다음과 같이 인터페이스에 타입 변수를 선언해 사용하면 된다.

//키페어를 저장하는 객체의 타입을 제네릭 인터페이스로 정의
interface KeyPair<K, V> {
  key: K;
  value: V;
}

다음과 같이 변수의 타입으로 정의해서 사용할 수 있다.

let keyPair: KeyPair<string, number> = {
  key: "key",
  value: 0,
};

let keyPair2: KeyPair<boolean, string[]> = {
  key: true,
  value: ["1"],
};

 

인덱스 시그니쳐와 함께 사용하기

제네릭 인터페이스는 인덱스 시그니쳐와 함께 사용하면 기존보다 훨씬 유연한 객체 타입을 정의할 수 있다.

interface Map<V> {
  [key: string]: V;
}

let stringMap: Map<string> = {
  key: "value",
};

let booleanMap: Map<boolean> = {
  key: true,
};
  • 한개의 타입 변수 V를 갖는 제네릭 인터페이스 Map을 정의했다.
    이 인터페이스는 인덱스 시그니쳐로 key의 타입은 string, value의 타입은 V인 모든 객체 타입을 포함하는 타입이다.
  • 변수 stringMap의 타입을 Map<string> 으로 정의했다.
    따라서 V가 string 타입이 되어 이 변수의 타입은 key는 string이고 value는 string인 모든 프로퍼티를 포함하는 객체 타입으로 정의된다.
  • 변수 booleanMap의 타입을 Map<boolean> 으로 정의했다.
    따라서 V가 boolean 타입이 되어 이 변수의 타입은 key는 string이고
    value는 boolean인 모든 프로퍼티를 포함하는 객체 타입으로 정의된다.

제네릭 타입 별칭

인터페이스와 마찬가지로 타입 별칭에서도 제네릭이 적용가능하다.

type Map2<V> = {
  [key: string]: V;
};

let stringMap2: Map2<string> = {
  key: "string",
};
  • 제네릭 타입 별칭을 사용할 때에도 제네릭 인터페이스와 마찬가지로 타입으로 정의될 때
    반드시 타입 변수에 설정할 타입을 명시해 주어야 한다.

제네릭 클래스

class List<T> {
  constructor(private list: T[]) {}

  push(data: T) {
    this.list.push(data);
  }

  pop() {
    return this.list.pop();
  }

  print() {
    console.log(this.list);
  }
}

const numberList = new List([1, 2, 3]);
const stringList = new List(["1", "2"]);
  • 클래스의 이름 뒤에 타입 변수를 선언하면 제네릭 클래스가 된다.
  • 이 타입 변수는 이제 클래스 내부에서 자유롭게 사용할 수 있다.
  • 클래스는 생성자를 통해 타입 변수의 타입을 추론할 수 있기 때문에
    생성자에 인수로 전달하는 값이 있을 경우 타입 변수에 할당할 타입을 생략해도 된다.

만약 타입변수의 타입을 직접 설정하고 싶다면 다음과 같이 하면 된다.

class List<T> {
  constructor(private list: T[]) {}

  (...)
}

const numberList = new List<number>([1, 2, 3]);
const stringList = new List<string>(["1", "2"]);
 

Promise 사용하기

Promise는 제네릭 클래스로 구현되어 있다. 따라서 새로운 Promise를 생성할 때 다음과 같이 타입 변수에 할당할 타입을 직접 설정해 주면 해당 타입이 바로 resolve 결과값의 타입이 된다.
const promise = new Promise<number>((resolve, reject) => {
  setTimeout(() => {
    // 결과값 : 20
    resolve(20);
  }, 3000);
});

promise.then((response) => {
  // response는 number 타입
  console.log(response);
});

promise.catch((error) => {
  if (typeof error === "string") {
    console.log(error);
  }
});
  • 아쉽게도 reject 함수에 인수로 전달하는 값 즉 실패의 결과값 타입은 정의할 수 없다.
    그냥 unknown 타입으로 고정되어 있기 때문에 catch 메서드에서 사용하려면 타입 좁히기를 통해 안전하게 사용하는걸 권장한다.

만약 어떤 함수가 Promise 객체를 반환한다면 함수의 반환값 타입을 위해 다음과 같이 할 수 있다.

function fetchPost(): Promise<Post> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 1,
        title: "게시글 제목",
        content: "게시글 본문",
      });
    }, 3000);
  });
}

 

반응형