Generic의 사전적인 의미로는 '포괄적인, 일반적인'이라는 뜻을 가지고 있습니다.
이 사전적 의미로 유추해 보면, Generic 타입은 여러 타입을 포괄적으로 다룬다는 뜻으로 해석할 수도 있을 것입니다.
Generic 타입은 실제 타입이 지정되지 않았다가, 사용될 때 실제 타입이 결정되는 특수한 타입입니다.
function tryGeneric<T>(message: T): T {
return message;
}
console.log( tryGeneric('Jeong') );
- 위 코드에서 tryGeneric 함수는 Generic 타입인 message를 인자로 받고 있고, Generic 타입의 반환 결과를 가지고 있습니다.
- 이때, tryGeneric('Jeong')을 사용하게 되면, string 타입의 인자가 전달되고, tryGeneric 함수의 T는 string 타입으로 결정됩니다.
그렇다면 Generic과 비슷한 타입인 any와는 어떤 차이가 있을까요?
function tryGeneric<T>(message: T): T {
message = 100; // 오류: 'T'은(는) 'number'과(와) 관련되지 않은 임의의 형식으로 인스턴스화할 수 있습니다.
return message;
}
console.log( tryGeneric('hello, world') );
function tryAny(message: any): any {
message = 100;
return message;
}
console.log( tryAny('hello, world') );
- Generic 타입의 경우, 전달받은 T라는 타입이 하나로 고정되기 때문에, 중간에 타입이 변하면 오류가 발생합니다.
- any 타입의 경우에도, 인자로 전달받을 땐 동일하게 어느 타입이든 받을 수 있지만, 리턴 타입도 any이기 때문에 중간에 타입이 변경되어도 문제가 없습니다.
- 따라서, 타입 검사를 좀 더 명확히 할 수 있다는 차이점이 있습니다.
이제 Generic 타입을 사용하는 방법에 대해 알아보겠습니다.
1. Generic 기본 사용법
- 여러 개의 Generic 타입을 동시에 사용할 수 있습니다.
- Generic 타입은 사용 방법에 따라, 명시적 또는 묵시적으로 정의될 수 있습니다.
function tryGeneric<T, U>(message: T, comment: U) {
return message;
}
tryGeneric<string, number>('hello', 100); // 명시적 타입 정의
tryGeneric('hello', 100); // 묵시적 타입 정의
2. Generic Array & Tuple
- Generic 타입을 Array나 Tuple로 사용할 수 있습니다.
function genericArray<T>(message: T[]): T {
return message[0];
}
genericArray(['Jeong', 'Hyeonjun']);
genericArray(['Jeong', 32]); // 리턴 타입이 정확히 추론되지 않는다.
function genericTuple<T, K>(message: [T, K]): T {
return message[0];
}
genericTuple(['Jeong', 'Hyeonjun']);
genericTuple(['Jeong', 32]); // 리턴 타입이 정확히 추론되기 때문에, 메서드 체이닝을 사용할 수 있다.
3. Generic Function
- Type Alias 또는 Interface에 Generic 타입을 적용해 함수의 규격을 만들 수 있습니다.
// Type Alias
type genericTypeAlias = <T>(message: T) => T;
const fn1: genericTypeAlias = <T>(message: T): T => {
return message;
}
// Interface
interface genericInterface {
<T>(message: T): T;
}
const fn2: genericInterface = <T>(message: T): T => {
return message;
}
4. Generic Class
- 클래스에도 Generic 타입을 적용할 수 있습니다.
class Person<T> {
constructor(private name: T) {
this.name = name;
}
setName(name: T) {
this.name = name;
}
}
const p1 = new Person(10);
const p2 = new Person('hello');
p1.setName('Jeong'); // 오류: 생성자에서 타입이 number로 결정되었기 때문에 문자열을 인자로 전달할 수 없음
5. Generic extends
- extends 키워드를 사용해서 Generic 타입의 범위를 제한할 수 있습니다.
class Person<T extends string | number> {
constructor(private name: T) {
this.name = name;
}
}
new Person('hello');
new Person(100);
new Person(true); // 오류: string 또는 number 타입을 전달해야 합니다.
6. keyof & type look-up system
- keyof 키워드는 타입 값에 존재하는 모든 프로퍼티의 키값을 union 형태로 리턴받습니다.
-
interface Person { name: string; age: number; } type person = keyof Person; // person 타입은 'name' | 'age'로 정의됨 const a: person = 'name';
-
interface Person {
name: string;
age: number;
}
const person: Person = {
name: 'Jeong',
age: 32
};
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}