본문 바로가기
프로그래밍/TypeScript

TypeScript - Generic

by hustle-ing 2023. 6. 1.

타입스크립트의 제네릭(Generic)은 코드 재사용성을 높이고 타입 안정성을 보장하는 기능이다. 제네릭을 사용하면 함수나 클래스를 작성할 때, 사용될 데이터의 타입을 미리 지정하지 않고, 이후에 함수나 클래스를 호출할 때 인자로 전달된 데이터의 타입에 따라 자동으로 타입을 추론하게 된다.

 

제네릭

function printLog<T>(text: T): T {
	return text;
}

printLog 함수에 T라는 타입 변수를 추가했다. T는 유저가 준 파라미터의 타입을 캡쳐하고, 이 정보를 나중에 사용할 수 있게 한다. 여기에서는 T를 반환 타입으로 다시 사용하였다. 즉, 파라미터와 반환 타입이 같은 타입을 사용하고 있음을 알 수 있다.

printLog 함수는 타입을 불문하고 동작하므로 제네릭이라 할 수 있다.

 

이렇게 제네릭을 작성하고 나면 아래와 같이 작성할 수 있다.

const str = printLog<string>('hello');

함수를 호출할 때의 인수 중 하나로써 T를 string 타입으로 명시해주고 타입 주변을 < >로 감싸 주었다.

혹은 타입 추론을 활용하여 아래와 같이 작성할 수도 있다.

const str = printLog('hello');

전달하는 인수에 따라 컴파일러가 자동으로 T의 값을 정하는 방법이다. 이는 타입이 복잡해져 컴파일러가 타입을 유추할 수 없게 되는 경우에는 사용할 수 없는 방법이다.

 

 

 

인터페이스와 제네릭

interface Item<T> {
	name: T;
	stock: number;
	selected: boolean;
}

이와 같이 작성하면 인터페이스를 여러개 만들지 않고도 재사용 할 수 있게 된다.

const obj: Item<string> = { 
	name: "T-shirts",
	stock: 2, 
	selected: false
};

const obj: Item<number> = { 
	name: 2044512,
	stock: 2, 
	selected: false
};

 

클래스와 제네릭

class GenericNumber<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; };

 

제네릭 타입 변수

제네릭을 사용하기 시작하면, printLog와 같은 제네릭 함수를 만들 때, 컴파일러가 함수 본문에 제네릭 타입화된 매개변수를 쓰도록 강요한다.

function printLog<T>(text: T): T {
	console.log(text.length);
	return text;
}

위와 같이 console.log(text.length)를 작성하게 되면 컴파일 에러가 난다. 개발자가 string 타입이 아닌 number 타입을 보낼 수도 있기 때문에, T에는 .length가 있다는 것을 추론할 수 없기 때문이다.

 

이때는 제네릭에 타입을 줘서 유연하게 함수의 타입을 정의해 줄 수 있다.

function printLog<T>(text: T[]): T[] {
	console.log(text.length);
	return text;
}

// 혹은

function printLog<T>(text: Array<T>): Array<T> {
	console.log(text.length);
	return text;
}

이 제네릭 함수는 일단 T라는 변수 타입을 받고, 인자 값으로는 배열 형태의 T를 받는다. 제네릭 타입이 배열이기 때문에, .legnth를 허용하게 된다.

 

제네릭 제약 조건

interface TextLength {
	length: number;
}

function printLog<T extends TextLength>(text: T): T {
	console.log(text.length);
	return text;
}

이와 같이 extends 지시자를 이용하면 타입에 대한 강제는 아니지만 length에 대해 동작하는 인자만 넘겨받을 수 있게 된다.

혹은 keyof를 이용해 제약을 줄 수도 있다.

interface Item<T> {
	name: T;
	stock: number;
	selected: boolean;
}

function printLog<T extends keyof Item>(text: T): T {
	return text;
}

printLog('name'); //정상
pirntLog('key'); //에러

 

댓글