제네릭 인터페이스는 제네릭 함수와 달리, 변수의 타입을 정의할 때 반드시 제네릭으로 타입을 작성해줘야 한다.
위 예시처럼 제네릭을 사용하여 타입을 선언해주면 된다. 타입 제한 없이 사용할 수 있다는 게 장점!
앞에서 한 번 다뤄봤던 인덱스 시그니처도 제네릭을 사용해서 위와 같이 사용할 수 있다. 타입이 달라졌을 때 추가로 선언하지 않고, 하나의 인터페이스로 계속해서 사용할 수 있다는 것이 제네릭의 장점인 것 같다.
학생 유저와 개발자 유저의 인터페이스를 가진 유저 관리 프로그램을 만든다고 해보자.
다음은 Student 인터페이스와 Developer 인터페이스 상위 인터페이스로 User 제네릭 인터페이스를 선언하고, 이를 적용한 코드이다.
interface Student {
type: "student";
school: string;
}
interface Developer {
type: "developer";
skill: string;
}
// 제네릭 인터페이스로 선언
interface User<T> {
name: string;
profile: T;
}
function goToSchool(user: User<Student>) {
// User 인터페이스를 제네릭 인터페이스로 선언함으로써 타입 가드가 필요 없어짐.
// 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"
}
};
const studentUser: User<Student> = {
name: "정수영",
profile: {
type: "student",
school: "대진대학교"
}
};
goToSchool(studentUser); // O
// goToSchool(developerUser); // X
User 인터페이스를 제네릭 인터페이스로 선언하지 않는다면, Student 객체만 인수로 받을 수 있는 goToSchool() 함수에서 Developer 객체가 인수로 들어오는 상황을 방지하기 위해 타입 가드
를 사용했어야 한다.
하지만 제네릭 인터페이스의 사용 덕분에 타입 가드 사용이 필요 없어졌다!
제네릭 클래스는 클래스를 타입 상관 없이 범용적으로 사용하고 싶을 때, 타입 변수를 사용해서 다음과 같이 선언할 수 있다. 클래스명 옆에 제네릭을 사용해 선언해주면 된다.
따라서 위와 같이 타입에 상관 없이 사용할 수 있다.
// Ex1
const numberList = new List([1, 2, 3]);
numberList.pop();
numberList.push(4);
numberList.print(); // [1, 2, 4]
// Ex2
const stringList = new List(["1", "2", "3"]);
stringList.pop();
stringList.push("4");
stringList.print(); // ['1', '2', '4'];
제네릭 클래스는 제네릭 Interface나 제네릭 타입 변수와는 다르게, 생성자의 인수로 전달하는 값을 기준으로 타입 변수의 타입을 추론한다.
따라서 객체 생성 시, 생성자 옆에 제네릭 타입을 명시하는 것이 필수는 아니다.
프로미스를 Typescript에서 사용할 때는 다음과 같이 사용한다.
resolve, reject는 인수의 타입을 추론할 수 있는 기능을 갖고 있지 않기 때문에, 이 때 프로미스와 제네릭을 함께 사용한다.
예시로 작성한 프로미스는 resolve에서 비동기 작업을 통해 반환하는 값이 number 타입이기 때문에, 제네릭을 사용해서 number 타입으로 지정하였다.
타입 변수를 사용하지 않으면 promise 인수의 타입은 기본적으로 unknown으로 추론되기 때문에, 꼭 지정해줄 것!
// 작업 성공 시
promise.then((response) => {
console.log(response); // 20
console.log(response * 10); // 200
})
// 작업 실패 시
promise.catch((err) => {
// err의 타입 역시 reject의 인수의 타입과 마찬가지로 any!
// 따라서 타입 가드를 활용해서 타입 좁히기를 해줘야 한다.
if (typeof err === "string") {
console.log(err);
}
});
자바스크립트에서의 프로미스와 마찬가지로, 비동기 작업 성공 시 할 작업은 then 메서드, 실패 시 할 작업은 catch 메서드를 통해 구현한다.
그리고 reject의 인수의 타입은 any 타입으로 추론되기 때문에, 타입 가드를 통해 타입 좁히기를 자주 사용한다!