제네릭
TypeScript는 정적 타입 언어이기 때문에 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언해야 한다.
그런데 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언하기 어려운 경우가 있따.
다음 예제는 FIFO 구조로 데이터를 저장하는 큐를 표현한 것이다.
class Queue {
protected data = []; // data: any []
push(item) {
this.data.push(item);
}
pop() {
return this.data.shift();
}
}
const queue = new Queue();
queue.push(0);
queue.push(1); // 의도하지 않은 실수!
console.log(queue.pop().toFixed()); // 0
console.log(queue.pop().toFixed()); // Runtime error
Queue 클래스의 data 프로퍼티는 타입 선언을 생략했기 때문에 any[] 타입이 된다. any[] 타입은 어떤 타입의 요소도 가질 수 있는 배열을 의미한다.
any[] 타입은 배열 요소의 타입이 모두 같지 않다는 문제가 있다.
위 예제의 경우 data 프로퍼티는 number 타입만을 포함하는 배열이라는 기대 하에 각 요소에 대해 Number.prototype.toFixed() 함수를 사용했다.
따라서 number 타입이 아닌 요소의 경우 런타임 에러가 발생한다.
이 문제를 해결하기 위해 Queue 클래스를 상속하여 number 타입 전용 NumberQueue 클래스를 정의할 수 있다.
class Queue {
protected data = []; // data: any []
push(item) {
this.data.push(item);
}
pop() {
return this.data.shift();
}
}
// Queue 클래스를 상속하여 number 타입 전용 NumberQueue 클래스를 정의
class NumberQueue extends Queue {
// number 타입의 요소만 push
push(item: number) {
super.push(item);
}
pop(): number {
return super.pop();
}
}
const queue = new NumberQueue();
queue.push(0);
// 의도하지 않은 실수를 사전에 검출 가능
// queue.push('1');
// [ts] Argument of type '"1"' is not assignable to parameter of type 'number'
queue.push(+'1');
console.log(queue.pop().toFixed()); // 0
console.log(queue.pop().toFixed()); // 1
이와 같이 numebr 타입 전용 NumebrQueue 클래스를 정의하면 number 타입 이외의 요소가 추가되었을 때 런타임 이전에 에러를 감지할 수 있다.
하지만 다양한 방법을 지원해야 한다면 타입 별로 클래스를 상속받아 추가해야 하므로 이 또한 좋은 방법은 아닌 것 같다.
제네릭을 사용하여 이 문제를 해결해보자.
class Queue<T> {
protected data : Array<T> = [];
push(item: T) {
this.data.push(item);
}
pop(): T {
return this.data.shift();
}
}
// number 전용 Queue
const numberQueue = new Queue<number>();
numberQueue.push(0);
// numberqueue.push('1'); // 의도하지 않은 실수를 사전 검출 가능
numberQueue.push(+'1'); // 실수를 사전 인지하고 수정할 수 있다
console.log(numberQueue.pop().toFixed()); // 0
console.log(numberQueue.pop().toFixed()); // 1
// string 전용 Queue
const stringQueue = new Queue<string>();
stringQueue.push('Hello');
stringQueue.push('Hi');
console.log(stringQueue.pop().toUpperCase()); // HELLO
console.log(stringQueue.pop().toUpperCase()); // HI
// 커스텀 객체 전용 Queue
const myQueue = new Queue<{name: string, age: number}>();
myQueue.push({name: 'Lee', age: 19});
myQueue.push({name: 'Oh', age: 19});
console.log(myQueue.pop()); // { name: 'Lee', age: 19 }
console.log(myQueue.pop()); // { name: 'Oh', age: 19 }
제네릭은 선언 지점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.
한 번의 선언으로 다양한 타입에 재사용이 가능하다는 장점이 있다. 사용법은 <타입명>으로 사용한다.
T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 '타입 파라미터' 라고 한다.
T는 Type의 약자로 반드시 T를 사용해야 하는 것은 아니다.
또한 함수에도 제네릭을 사용할 수 있는데 제네릭을 사용하면 하나의 타입만이 아닌 다양한 타입의 매개변수와 반환값을 사용할 수 있다.
function reverse<T>(item: T[]): T[] {
return item.reverse();
}
reverse 함수는 인수의 타입에 의해 타입 매개변수가 결정된다.
reverse 함수는 다양한 타입의 요소로 구성된 배열을 인자로 전달받는다.
예를 들어 number 타입의 요소를 갖는 배열을 전달받으면 타입 매개변수는 number가 된다.
// number 타입의 요소를 갖는 배열을 전달받으면 타입 매개변수는 number가 된다
function reverse<T>(item: T[]): T[] {
return item.reverse();
}
const arg = [1, 2, 3, 4, 5];
// 인수에 의해 타입 매개변수가 결정됨
const reversed = reverse(arg);
console.log(reversed); // [5, 4, 3, 2, 1]
만약 { name: string } 타입의 요소를 갖는 배열을 전달받으면 타입 매개변수는 { name: string }이 된다.
// { name: string } 타입의 요소를 갖는 배열을 전달받으면 타입 매개변수는 { name: string } 이 된다
function reverse<T>(item: T[]): T[] {
return item.reverse();
}
const arg = [{ name: 'Lee' }, { name: 'Oh' }];
// 인수에 의해 타입 매개변수가 결정된다
const reversed = reverse(arg);
console.log(reversed); // [ { name : 'Lee' }, { name: 'Oh' } ]
'TypeScript' 카테고리의 다른 글
인터페이스 (0) | 2019.08.27 |
---|---|
클래스 (0) | 2019.08.26 |
TypeScript 정적 타이핑 (0) | 2019.08.25 |
TypeScript??? (0) | 2019.08.24 |