[TypeScript] 섹션7. 제네릭(2)

jaehoon ahn·2025년 2월 17일

TypeScript

목록 보기
11/14
post-thumbnail

제네릭 인터페이스 & 제네릭 타입 별칭

인터페이스 정의 방법

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

인덱스 시그니쳐

⇒ key 타입, value 타입, 프로퍼티의 키와 밸류의 규칙만 충족한다면 사용할 수 있는 문법

interface NumberMap {
  [key: string]: number;
}
let numberMap1: NumberMap = {
  key: -1231,
  key2: 123123,
};

인덱스 시그니쳐 + 제네릭 인터페이스

⇒ 하나의 타입으로 다양한 객체를 표현할 수 있다.

interface Map<V> {
  [key: string]: V;
}
let stringMap: Map<string> = {
  key: "value",
};
let booleanMap: Map<boolean> = {
  key: true,
};

// 제네릭 타입 별칭
type Map2<V> = {
  [key: string]: V;
};
let stringMap2: Map2<string> = {
  key: "hello",
};

제네릭 인터페이스의 활용 예시

⇒ 유저 관리 프로그램

⇒ 유저 구분: 학생 유저 / 개발자 유저

⇒ 타입들의 유형을 깔끔하게 분리할 수 있다.

interface Student {
  type: "student";
  school: string;
}
interface Developer {
  type: "developer";
  skill: string;
}

interface User<T> {
  name: string;
  profile: T;
}
function goToSchool(user: User<Student>) {
  //   if (user.profile.type !== "student") {
  //     console.log("잘 못 오셨습니다.");
  //     return;
  //   }
  // 제네릭 인터페이스가 없을 때, 조건식으로 타입 좁히기를 해야하지만,
  // 제네릭 인터페이스를 사용하면 코드를 간단하게 하여 타입 좁히기가 가능하다.
  const school = user.profile.school;
  console.log(`${school}로 등교 완료`);
}
const developerUser: User<Developer> = {
  name: "제노",
  profile: {
    type: "developer",
    skill: "typescript",
  },
};
// goToSchool(developerUser); // developerUser는 Student 타입 유저에 넣을 수 없기 때문에 오류 발생
const studentUser: User<Student> = {
  name: "홍길동",
  profile: {
    type: "student",
    school: "중앙대학교",
  },
};

제네릭 클래스

클래스

class NumberList {
  constructor(private list: number[]) {}

  push(data: number) {
    this.list.push(data);
  }
  pop() {
    return this.list.pop();
  }
  print() {
    console.log(this.list);
  }
}

const numberList = new NumberList([1, 2, 3]);
numberList.pop();
numberList.push(4);
numberList.print();

⇒ string list도 있다고 가정

⇒ 타입을 다 number로 고정시켜놨기 때문에, 모두 바꿔줘야 한다.

⇒ 이를 제네릭 클래스로 해결

class List<T> {
  // 범용적으로 사용할 수 있도록 List로 이름 변경
  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]);

numberList.pop();
numberList.push(4);
numberList.print();

const stringList = new List(["1", "2"]);

stringList.push("hello");
stringList.print();

프로미스와 제네릭

프로미스

const promise = new Promise<number>((resolve, reject) => {
  setTimeout(() => {
    // resolve(20);
    reject("~~ 때문에 실패");
  }, 3000);
});

promise.then((response) => {
  console.log(response * 10); // 20, number 타입으로 할당
});

promise.catch((err) => {
  if (typeof err === "string") {
    console.log(err);
  }

 
});

⇒ 프로미스는 제네릭 클래스를 기반으로 타입이 선언되어 있기 때문에, 타입 변수로 비동기 처리 결과값의 타입을 정의해줄 수 있지만, 실패했을 때 타입은 정의해줄 수 없다.

⇒ 따라서 타입 좁히기를 해야한다.

프로미스를 반환하는 함수의 타입을 정의

interface Post {
  id: number;
  title: string;
  content: string;
}
// Promise가 클래스 이기 때문에, 타입으로 사용할 수 있다.
// 반환값 타입으로 정의해줄 수 있다.
function fetchPost(): Promise<Post> {
  return new Promise((resolve, reject) => {
    // new Promise<Post> 이렇게 직접 Post 타입을 정의해줘도 된다.
    setTimeout(() => {
      resolve({
        id: 1,
        title: "게시글 제목",
        content: "게시글 컨텐츠",
      });
    }, 3000);
  });
}
const postRequest = fetchPost();
postRequest.then((post) => {
  post.id;
});

0개의 댓글