사용자 도구

사이트 도구


typescript:generic

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<T>(arg: T): T {
    return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
let a: <U>(arg: U): U = identity;
let myIdentity: {<T>(arg: T): T} = identity;  // Generic type parameter의 이름을 다르게 사용해도 됨

Generic interface

interface GenericIdentityFn {
  <T>(arg: T): T;
}
function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: GenericIdentityFn = identity;
 
let myIdentity: GenericIdentityFn<number> = identity; // type을 number로 고정시킴

Generic interface이외에도 Generic class도 만들 수 있지만, generic enum이나 generic namespace를 만들 수 는 없다.

Generic Classes

class Generic<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y };

Generic class는 오직 객체에서만 generic 사용가능하다. 그렇기 때문에 class작업을 할 때 정적 멤버들은 generic사용이 안된다는 점을 유념하자

Generic Constraints

특정한 타입들(내부 메서드는 알고 있는)과 작동하는 generic함수를 만들고 싶을지 모른다. 예제에서 .length속성을 접근하고 싶었지만 모든 타입이 .length속성을 가지고 있는 것이 아니라서 컴파일러는 에러메세지를 보내준다.

function loggingIdentity<T>(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<T extends Lengthwise>(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<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"); // 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<T>(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<A extends Animal>(c: new() => A): A {
    return new c();
}
createInstance(Lion).keeper.nametag; //typecheck!
createInstance(Bee).keeper.hasMask; //typecheck!
typescript/generic.txt · 마지막으로 수정됨: 2025/04/15 10:05 저자 127.0.0.1