본문 바로가기

javascript/typescript

[타입스크립트] Generics . 제네릭

C++, C#, Java 같은 언어들에서는 컴포넌트의 재사용성을 증진시키기 위해 template, 혹은 generic 문법을 지원한다.

타입스크립트에서도 이런 편리성을 위해 제네릭 문법을 지원한다.

 

 

Documentation - Generics

Types which take parameters

www.typescriptlang.org

 

타입스크립트의 근원이 되는 자바스크립트는 기본적으로 동적 타입 기반의 언어이므로 제네릭 문법이 필요하지 않다. 우선 특정 변수나 객체의 타입을 명시하는 방법이 언어 측면에서 지원되지 않고, 특정 타입을 함수 등의 인수에 강제하지도 않기 때문이다. 단지 해당 인수의 기능을 외워 함수 안에서 구현하기만 하면 된다.

 

function howAreYou(arg: string): string {
	return arg;
}

// 자바스크립트 코드의 방식
function howAreYou2(arg: any): any {
	return arg;
}

 

 

위 코드의 howAreYou와 howAreYou2를 보자. howAreYou의 경우 string을 인수로 받는다. 이때 인수 arg는 string이라는 타입의 정보가 함수로 들어오는 과정에서 소멸되지 않아, 내부에서 사용 가능하다.

 

반면 howAreYou2 함수의 경우, arg로 any 타입을 받는다. 즉, 아무 타입도 가리지 않고 받겠다는 의미로 해석된다. 물론 내부에서 타입을 강제하거나 type guard를 통해 특정하면 타입 정보를 살려낼 수 있지만, 기본적으로 any 타입에는 어떤 타입 정보도 담기지 않는다.

 

단지 아무 인수나 함수에 받을 수 있도록 구현하는 것이 제네릭의 원칙이라면, 타입스크립트는 자바스크립트와 마찬가지로 제네릭이 필요 없을 것이다. 그러나, 다른 언어의 제네릭이 단순히 모든 타입의 인수에 대응하는 것인가? 아니다.

 

제네릭은 다양한 인수를 받을 수 있도록 하는 것이 목적이기는 하지만, any 타입의 동작과는 달리 인수로 전달되는 값이 자신의 타입을 잃지 않고 전달된 위치에서도 자신의 타입을 유지한다. 만약 제네릭 함수에 대해 string 변수를 인자로 담아 보내면, 해당 인자는 string 정보를 잃지 않고 'toLocaleLowerCase' 과 같은 함수를 사용할 수 있어야 한다.

function whoAreYou<T>(arg: T): T {
	return arg;
}

let output = whoAreYou("my string");

 

제네릭 함수에 의해 타입 정보를 잃지 않고 반환되었다.

제네릭 타입은 다음과 같은 방식으로 표현할 수 있다

//1. 단순 함수 선언 방식
let output1: <T>(arg: T) => T = whoAreYou; 

//2. 객체 리터럴 형식
let output2: { <T>(arg: T) : T} = whoAreYou; 

//3. 인터페이스의 모든 멤버가 T를 알 수 있음
interface IGenWAY<T> {
    (arg: T) : T;
}
let output3: IGenWAY<string> = whoAreYou;

//4. 객체 리터럴을 인터페이스화
interface IGenWAY2 {
    <T>(arg: T) : T;
}
let output4: IGenWAY2 = whoAreYou;

 

제네릭 제약조건

 

제네릭을 사용하면 다양한 변수에 대해 동작할 수 있다. 이때 특정 기능을 지니는 변수로 제네릭을 한정하고 싶을 수도 있다. 예를 들어 우리는 제네릭 함수의 매개변수로 length 프로퍼티를 가진 객체를 원할 수 있다. 이런 경우 extends 제약조건을 이용하여 제네릭 대상에 제한을 걸 수 있다.

 

//1. 제네릭 제약조건
interface ILen {
	length : number;
}

function loggingIdentity<T extends ILen>(arg: T): T {
	console.log(arg.length);
    return arg;
}

위의 경우, length 프로퍼티가 없는 변수는 loggingIdentity로 넘길 수 없다.

 

//2. 제네릭 제약조건에서 타입 매개변수 사용

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}

//3. 제네릭 대상을 클래스만으로 한정하기 위한 인터페이스
interface IClass {
	new (args: any[]) : any;
}

 

결론

타입스크립트는 타 언어들처럼 제네릭을 지원한다.

이때 제네릭의 대상을 extends 키워드를 통해 제약을 설정함으로써 제한할 수 있다.