제네릭은 Java 등의 정적 타입 언어를 사용하던 사람에게는 익숙한 단어 일지 모른다.
제네릭은 어떠한 클래스 혹은 함수에서 사용할 타입을 그 함수나 클래스를 사용할 때 결정하는 기법이다. 선언 시점에서 타입을 정의하는 것이 아니라 사용 시점에 정하기 때문에 함수나 클래스를 범용적으로 사용할 수 있다.
class Stack {
private data: any[] = [];
constructor() {}
push(item: any): void {
this.data.push(item);
}
pop(): any {
return this.data.pop();
}
}
이러한 스택 구조가 있을 때 어떠한 값이 들어 올지 모르기 때문에 any 타입으로 지정했다. 그러나 타입의 추론이 불가하고 데이터의 타입이 제각각이 될 수 있다. 그래서 런타임에 타입을 검사 하는 코드가 필요해 진다.
그렇다고 자료형을 보장하기 위해 number만 받는다면? 범용성이 떨어 진다. 이런 경우 제네릭을 사용하여 해결 할 수 있다.
class Stack<T> {
private data: T[] = [];
constructor() {}
push(item: T): void {
this.data.push(item);
}
pop(): T | undefined {
return this.data.pop();
}
}
T
는 Type의 약자로 다른 언어에서도 제네릭을 선언할 때 관용적으로 많이 사용된다. 여기에서 T
를 타입 변수(Type variable) 라고 한다.
사용방법은 아래와 같이 생성자를 호출하여 인스턴스를 만들 때 T
로 사용될 타입을 꺽쇠에 지정해 주면 된다.
const s = new Stack<number>();
s.push(1);
배열을 입력으로 받아 그 배열의 첫번째 요소를 출력하는 lodash.head()
같은 함수를 구현해 보자. 제네릭을 사용하지 않는 경우 이렇게 할 것이다.
function first(arr: any[]): any {
return arr[0];
}
리턴 타입을 알 수가 없다. 바꿔 보자.