Skip to main content

제네릭

간략히#

  • C# 이나 Java와 같이, 재사용가능한 컴포넌트를 만드는 기능을 제네릭이라 합니다. java에서 클래스 내부에서 사용할 데이터 타입을 외부에서 지정했던 것처럼 비슷하게 typescript에서 함수나 클래스에 제네릭이 쓰입니다.
//기존의 인자타입 지정 방법
//특정타입
function identity(arg: number): number {
return arg;
}
//any
function identity(arg: any): any {
return arg;
}
//제네릭
function identity<T>(arg: T): T {
return arg;
}
  • any와는 다르게 type 정보를 잃지 않습니다.
let output = identity<string>('myString'); //output will be string type
  • 타입 추론이 작용한 예
let output = identity('myString');
  • 배열의 경우
//올바르지 못한 코드
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
//올바른 코드
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
//혹은
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}

제네릭 함수와 인터페이스#

  • 제네릭이 아닌 함수의 선언과 비슷합니다.
  • 타입지정 할 때에 필요에 따라 다른 이름의 제네릭 타입을 주기도 합니다.
  • 리터럴 타입도 가능합니다.
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
let yourIdentity: <U>(arg: U) => U = identity;
let JackIdentity: { <T>(arg: T): T } = identity;
  • 이는 인터페이스로 만들 수 있다는 말과 같습니다.
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
// 타입이 제너릭이 아닌데도 사용하는 법
let yourIdentity: GenericIdentityFn<number> = identity;

제네릭 클래스#

  • number뿐 아니라 다른 것도 사용 가능합니다.
  • instance side 에서만 사용 가능합니다. (not for static side)
class GenericClass<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericClass<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
let stringNumeric = new GenericClass<string>();
stringNumeric.zeroValue = '';
stringNumeric.add = function (x, y) {
return x + y;
};

제네릭 제약#

  • interface로 제네릭을 제약할 수 있습니다. (extends)
  • 예시에서는 Array로 지정을 안해주더라도 프로퍼티로 length를 포함하고 있는 객체라면 loggingIdentity함수에 접근할 수 있도록 해주었습니다.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
  • 다른 제네릭 파라미터로 제약할 수도 있습니다.
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, 'a'); // okay
getProperty(x, 'm'); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

클래스 타입을 사용한 제네릭#

  • 팩토리를 만들 때
function create<T>(c: { new (): T }): T {
return new c();
}
  • constructor 함수와 instance side 관계를 제약할 때
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!