제네릭 (Generic)
제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용됩니다.
타입스크립트는 정적 타입 언어이기 때문에 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언하여야 합니다. 그런데 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언하기 어려운 경우가 있습니다.
class Queue {
protected data: any[] = [];
push(item: any) {
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[]타입은 배열 요소의 타입이 모두 같지 않다는 문제가 있는데, 위 예제의 경우 number타입만을 포함하는 배열이라는 기대 하에 각 요소에 대해 Number.protoype.toFixed를 사용했습니다. 따라서 number 타입이 아닌 경우 런타임 에러가 발생합니다.
위와 같은 문제를 해결하기 위해 Queue 클래스를 상속하여 number 타입 전용 NumberQueue 클래스를 정의할 수 있습니다.
class Queue {
protected data: any[] = [];
push(item: any) {
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);
// 의도하지 않은 실수를 사전 검출 가능
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
// queue.push('1');
queue.push(+'1'); // 실수를 사전 인지하고 수정할 수 있다
console.log(queue.pop().toFixed()); // 0
console.log(queue.pop().toFixed()); // 1
하지만 number 타입 이외의 요소가 추가되었을 때, 아래와 같이 런타임 이전에 에러를 감지할 수 있습니다.
이러한 문제를 해결하기 위해 제네릭을 사용할 수 있습니다.
class Queue<T> {
protected data: Array<T> = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.shift();
}
}
// number 전용 Queue
const numberQueue = new Queue<number>();
numberQueue.push(0);
// numberQueue.push('1'); // 의도하지 않은 실수를 사전 검출 가능
numberQueue.push(+'1'); // 실수를 사전 인지하고 수정할 수 있다
// ?. => optional chaining
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining
console.log(numberQueue.pop()?.toFixed()); // 0
console.log(numberQueue.pop()?.toFixed()); // 1
console.log(numberQueue.pop()?.toFixed()); // undefined
// string 전용 Queue
const stringQueue = new Queue<string>();
stringQueue.push('Hello');
stringQueue.push('World');
console.log(stringQueue.pop()?.toUpperCase()); // HELLO
console.log(stringQueue.pop()?.toUpperCase()); // WORLD
console.log(stringQueue.pop()?.toUpperCase()); // undefined
// 커스텀 객체 전용 Queue
const myQueue = new Queue<{name: string, age: number}>();
myQueue.push({name: 'Lee', age: 10});
myQueue.push({name: 'Kim', age: 20});
console.log(myQueue.pop()); // { name: 'Lee', age: 10 }
console.log(myQueue.pop()); // { name: 'Kim', age: 20 }
console.log(myQueue.pop()); // undefined
제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법입니다. T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 타입 파라미터라 합니다. T는 Type의 약자로 반드시 T를 사용하여야 하는 것은 아닙니다.
함수에도 제네릭을 사용할 수 있는데 제네릭을 사용하면 하나의 타입만이 아닌 다양한 타입의 매개변수와 리턴값을 사용할 수 있습니다.
function reverse<T>(items: T[]): T[] {
return items.reverse();
}
const arg = [1, 2, 3, 4, 5];
// 인수에 의해 타입 매개변수가 결정된다.
const reversed = reverse(arg);
console.log(reversed); // [ 5, 4, 3, 2, 1 ]
const arg = [{ name: 'Lee' }, { name: 'Kim' }];
// 인수에 의해 타입 매개변수가 결정된다.
const reversed = reverse(arg);
console.log(reversed); // [ { name: 'Kim' }, { name: 'Lee' } ]
'프론트엔드 스터디 > Typescript' 카테고리의 다른 글
[TypeScript] 기초 - 타입 앨리어스(Type Alias) (0) | 2021.07.21 |
---|---|
[TypeScript] 기초 - 타입 추론(Type Inference) (0) | 2021.07.21 |
[TypeScript] 기초 - 교차 타입(Intersection Type) (0) | 2021.07.21 |
[TypeScript] 기초 - 유니온 타입(Union Type) (0) | 2021.07.21 |
[TypeScript] 기초 - 리터럴 타입 (0) | 2021.07.20 |