제네릭

정민교·2023년 10월 19일
0

typescript

목록 보기
15/17

📒제네릭

함수나 인터페이스, 타입 별칭, 클래스 등에서 다양한 타입을 사용해서 동작할 수 있도록 해주는 문법입니다.

✔️제네릭 함수

function func(value: any) {
  return value;
}

let num = func(10);
// any 타입

let str = func("string");
// any 타입

매개변수에 다양한 타입을 제공받게 하기 위해 any 타입으로 설정했습니다. unknown 타입으로도 가능합니다.

그리고 위 함수는 매개변수를 그대로 반환하는 함수입니다.

함수의 결과로 num에는 10, str은 string 이 할당됩니다.

하지만 numstr의 타입은 any 타입으로 추론됩니다.

함수의 반환값 타입은 return문 기준으로 추론되기 때문입니다.

매개변수 valueunknown 타입으로 설정해도 마찬가지로 numstrunknown 타입으로 추론됩니다.

이 같은 문제 때문에 다음과 같은 예측하지 못하는 상황이 발생하게 됩니다.

function func(value: any) {
  return value;
}

let num = func(10);
let str = func("string");

num.toUpperCase()

function func(value: unknown) {
  return value;
}

let num = func(10);
// unknown 타입

let str = func("string");
// unknown 타입

num.toUpperCase(); // ❌
num.toFixed(); // ❌

각 타입이 any 혹은 unknown 타입이기 때문에 이 타입의 메서드가 아니어도 호출 가능한 상황이 나오기도 하고, 해당 타입의 메서드가 맞는데도 호출 불가능한 상황이 나오기도 합니다.

이를 위해서 필요한 것이 제네릭 함수입니다.

제네릭은 '포괄적인' 이라는 의미가 있습니다. 즉, 제네릭은 타입을 일반화하여 다양한 타입을 적용할 수 있도록 하기 위해 사용됩니다.

이 제네릭을 활용한 제네릭 함수도 마찬가지로 다양한 타입의 값을 적용하여 사용할 수 있는 범용적인 함수입니다.

function func<T>(value: T): T {
  return value;
}

let num = func(10);
// number 타입

위와 같이 제네릭 타입(타입 변수)를 선언할 수 있고 타입 변수에 어떤 타입이 할당될 지는 함수 호출 시에 결정됩니다.

즉, 매개변수에 제공되는 값을 기준으로 타입 변수의 타입을 추론합니다.

10을 인수로 넘겨 호출했기 때문에 Tnumber 타입으로 추론됩니다.

제네릭 함수를 호출할 때 타입 변수에 할당할 타입을 직접 명시하는 것도 가능합니다.

function func<T>(value: T): T {
  return value;
}

let arr = func<[number, number, number]>([1, 2, 3]);

T에 튜플 타입이 할당되고 매개변수와 반환값 타입 모두 튜플 타입이 됩니다.

📒제네릭 인터페이스

제네릭은 인터페이스에도 적용할 수 있습니다.

인터페이스에 타입 변수를 선언하여 사용하면 됩니다.

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

stringMap은 key는 string 타입이고 value는 string 타입인 모든 프로퍼티를 포함하는 객체입니다.

booleanMap도 마찬가지입니다.

📒제네릭 타입 별칭

타입 별칭에도 제네릭을 적용할 수 있습니다.

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"]);

클래스 이름 뒤에 꺽쇠와 함께 타입 변수를 선언하면 됩니다.

클래스는 생성자를 통해 타입 변수의 타입을 추론할 수 있습니다. 따라서 클래스를 통해 인스턴스를 생성할 때 인수를 전달하는 값이 있는 경우 타입 변수에 할당할 타입을 생략할 수 있습니다.

📒프로미스와 제네릭

자바스크립트의 내장 클래스 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);
  }
});

비동기 처리(서버로부터 데이터를 받아오는 등)를 할 때에는 보통 어떤 객체를 받아오게 됩니다.

특정 객체의 타입을 미리 정의하고 해당 타입을 Promise의 결과값 타입으로 타입 변수를 설정하면 됩니다.

interface Post {
  id: 1,
  title: '게시글 제목'
  content: '게시글 본문'
}

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

Promise의 생성자 함수에 타입 변수를 할당하는 것 대신 비동기 처리를 진행하는 함수 fetchPost 함수의 결과값 타입을 제네릭을 포함한 Promise 타입을 반환하도록 하면 한 눈에 어떤 결과값을 반환하는지 파악하기 좋습니다.

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

0개의 댓글