지난 포스트에서 제네릭이 무엇인지에 대해 알아보았습니다. 그리고 제네릭은 함수, 인터페이스, 클래스, 타입 별칭에도 사용할 수 있다고 했었습니다.
그리고 예시를 함수로만 들었었는데 함수만 하고 넘어가긴 좀 그런거 같아서 인터페이스, 클래스, 타입 별칭에서 제네릭을 어떻게 사용하는지도 소개하고자 합니다.
제네릭 인터페이스
는 다음과 같이 정의합니다. 인터페이스 이름 뒤에 꺾쇠 열고 타입 변수를 선언합니다.
interface 인터페이스_명<타입_변수> {}
제네릭 인터페이스
를 사용할 때는 반드시 꺾쇠 안에 타입을 지정해 주어야 합니다. 인터페이스는 타입 추론을 할만한 여지가 없기 때문에 타입을 지정하지 않으면 예상치 못한 동작이 발생하기 때문입니다.
interface Point<X, Y> {
x: X;
y: Y;
}
let point: Point<number, number> = { //반드시 타입 지정!
x: 1,
y: 1,
};
인터페이스에서는 인덱스 시그니쳐
문법을 사용할 수 있었던 것 기억 하시나요? 제네릭 인터페이스
에서도 인덱스 시그니쳐
를 사용할 수 있습니다.
interface KeyPair<V> {
[key: string]: V;
}
let stringKeyPairs: KeyPair<string> {
key: 'aaa',
};
let numberKeyPairs: KeyPair<number> {
key: 1,
}
기존의 인덱스 시그니쳐
에서는 한 번 정한 타입으로만 선언할 수 있었지만, 제네릭 인터페이스
를 이용하면 value에 사용되는 타입이 선언 과정에서 정의되므로 더욱 자유로운 용법으로 사용할 수 있습니다.
제네릭 클래스
는 다음과 같이 정의합니다.
class 클래스_명<타입_변수> {}
제네릭 클래스
는 비슷한 내용의 클래스를 간편하게 정의할 수 있게 해줍니다. 예를들면 다음과 같이 숫자 리스트와 문자열 리스트 두 개의 클래스를 만들어야하는 상황을 단 하나의 클래스 정의로 해결하게 해줍니다.
class stringDataList {
constructor(private list: string[]) {}
(...)
}
class numberDataList {
constructor(private list: number[]) {}
(...)
}
class dataList<T> {
constructor(private list: T[]) {}
(...)
}
let dataList1 = new List(['', '', '']);
let dataList2 = new List([1, 2, 3]);
이처럼 제네릭 클래스
를 정의하면 전달하는 데이터 값에 따라 자동적으로 타입 추론을 해서 하나의 클래스로 여러 타입의 인스턴스 객체를 만들어낼 수 있습니다.
제네릭 타입 별칭
은 다음과 같이 정의합니다.
type 타입_별칭_명<타입_변수> = {};
제네릭 타입 별칭
또한 정의 과정에서 타입 변수에 타입을 명시해주어야 합니다.
type Point<X, Y> = {
x: X;
y: Y;
};
let point: Point<number, number> = { //반드시 타입 지정!
x: 1,
y: 1,
};