====== Generics ======
JavaScript는 타입 선언이 필요하지 않고, 따라서 특정 타입을 위해 만들어진 클래스나 함수도 타입 에러를 런타임에서 일으킬 뿐이다. 코드를 실행시키기 전까지는 함수와 클래스가 모든 타입에 대응한다. 따라서 JavaScript에서는 제네릭이란 말을 듣지 못 했다.
==== 제네릭을 사용하는 이유 ====
''any''를 이용하여 구현하는 방법이 있겠으나 제네릭과는 차이가 있다.
let data: any[] = [];
function pushItem(item: any): void {
any.push(item);
}
''data''의 타입이 any이므로 [0,'a string']라는 상이한 type을 가질 수 있다.
let data: T[] = []; //generic type T를 갖는 배열
이제 data는 하나의 타입을 갖는 배열이 된다.
===== Generic Types =====
function identity(arg: T): T {
return arg;
}
let myIdentity: (arg: T) => T = identity;
let a: (arg: U): U = identity;
let myIdentity: {(arg: T): T} = identity; // Generic type parameter의 이름을 다르게 사용해도 됨
===== Generic interface =====
interface GenericIdentityFn {
(arg: T): T;
}
function identity(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
let myIdentity: GenericIdentityFn = identity; // type을 number로 고정시킴
Generic interface이외에도 Generic class도 만들 수 있지만, generic enum이나 generic namespace를 만들 수 는 없다.
===== Generic Classes =====
class Generic {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y };
Generic class는 오직 객체에서만 generic 사용가능하다. 그렇기 때문에 class작업을 할 때 정적 멤버들은 generic사용이 안된다는 점을 유념하자
===== Generic Constraints =====
특정한 타입들(내부 메서드는 알고 있는)과 작동하는 generic함수를 만들고 싶을지 모른다. 예제에서 .length속성을 접근하고 싶었지만 모든 타입이 .length속성을 가지고 있는 것이 아니라서 컴파일러는 에러메세지를 보내준다.
function loggingIdentity(arg: T): T {
console.log(arg.length); // error : T dosen't have .length
return arg;
}
any와 모든 타입과 작동하는 대신에 우리는 .length속성이 있는 모든 타입과 any와 작동하는 함수로 제한하고 싶을지도 모른다. 이 타입이 .length를 가지는 한 허용할 것할 것이고, .length멤버는 필수입니다. 이렇게 하기 위해서, 반드시 T가 무엇이 되어야 하는지 **제한**을 두어야 합니다.
제한을 두기 위해서, 제한을 구현시키는 interface을 만들 것이다. 여기서 .length속성이 있는 interface를 만들 것이고 그 다음 extends키워드를 통해 제한을 나타낸다.
interface Lengthwise {
length: number;
}
function loggingIdentity(arg: T): T
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
이제 제한을 걸었기 때문에 아무 타입이나 작동하지 않는다.
loggingIdentity(3); // Error, number doesn't have a .length property
대신에 우리는 모든 type 요구조건이 충족된 값을 인자값으로 넘겨야 한다.
loggingIdentity({length: 10, value: 3});
===== Using Type parameters in Generic Constraints =====
다른 type인수에 의해 제한되는 type인수를 선언한다. 예를 들자면, 주어진 이름으로 객체의 속성을 얻고 싶다고 하자. 실수로라도 객체에 존재하지 않는 다른 속성을 갖지 않도록 하고 싶다. 그래서 2개의 타입에 제한을 둘 것이다.
function getProperty(obj: T, key: K) {
return obj[key];
}
let x = {
a: 1,
b: 2,
c: 3,
d: 4
}
getProperty(x, "a"); // Ok
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
===== Using Class Types in Generics =====
Generic을 이용해서 Factory를 만들때, 클래스타입들을 그들의 생성자 함수에 의해 참조하는 것은 필수적입니다.
function create(c: {new(): T}): T {
return new c();
}
더 고급 예시는 클래스의 생성자 함수와 객체측 사이에서의 관계를 제한하고 추론하기 위해 프로토타입 속성을 사용한다.
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(c: new() => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; //typecheck!
createInstance(Bee).keeper.hasMask; //typecheck!