TypeScript 기본 : 제네릭 함수

2024. 1. 30. 03:26

제네릭(generic)함수란?

제네릭은 일반적이고 포괄적인 것을 말한다. 쉽게 말해 모든 타입에 사용할 수 있는 범용적인 함수를 뜻한다.
이미 범용적으로 사용할 수 있는 any타입이 있지만, 알다시피 any타입에는 많은 위험성이 있다.

TypeScript 공식 핸드북에서는 이렇게 말하고 있다.

 

any를 쓰는 것은 함수의 arg가 어떤 타입이든 받을 수 있다는 점에서 제네릭이지만,
실제로 함수가 반환할 때 어떤 타입인지에 대한 정보는 잃게 됩니다.
만약 number 타입을 넘긴다고 해도 any 타입이 반환된다는 정보만 얻을 뿐입니다.
대신에 우리는 무엇이 반환되는지 표시하기 위해 인수의 타입을 캡처할 방법이 필요합니다.
여기서는 값이 아닌 타입에 적용되는 타입 변수 를 사용할 것입니다.

 

 

any를 사용하지 않고 타입을 명시적으로 나타내기 위한 타입가드 등의 방법도 있지만 번거롭다. 
대신 JS를 사용할 때 처럼 호출할 때 마다 타입이 추론되게 하는 것이 바로 제네릭이다.

 

function func(value:any) {
	return value;
}

let num = func(10);
let str = func('something');
//let num:any
//let str:any

 

func의 매개변수 value를 any타입으로 지정하면 오류는 발생하지 않지만, num이 any타입으로 추론된다.
그렇다고 number타입으로 지정하기에는, func함수를 여러번 사용하기 골치아프다.

 

function func<T>(value: T): T {
  return value;
}

let num = func(10);
let str = func('something');
let arr = func<[number, number, number]>([1, 2, 3]);
//let num: number
//let str: string
//let arr: [number,number,number]

 

그럴 때 함수이름 옆에 꺽쇠로 타입을 넣고 <Type> 매개변수와 반환값도 Type으로 지정하면
범용성있게 추론되는 제네릭 함수가 완성된다. 

만약 튜플타입으로 추론되게끔 지정하고 싶다면, 함수이름 옆에 꺽쇠로 튜플을 작성하면 된다.

 

매개변수의 타입이 복수인 경우

function swap<T>(a: T, b: T) {
	return [b, a];
}
const [a, b] = swap('1', 2); 
// 2는 string이 아니기 때문에 할당할 수 없다

 

현재 T 타입에는 string이 할당되어 있어서 a와 b 모두를 string타입으로 추론한다.
그렇기 때문에 swap('1', 2)에서 2는 오류가 발생한다. 

 

function swap<T, U>(a: T, b: U) {
	return [b, a];
}
const [a, b] = swap('1', 2);

 

이럴 땐 새로운 제네릭 타입을 꺽쇠에 콤마로 구분해서 만들고, 각 매개변수에 지정해주면 된다. 

 

 

매개변수가 다양한 타입의 배열인 경우

function returnFirstValue<T>(data: T[]) {
  return data[0];
}

let num = returnFirstValue([0, 1, 2]);
// number

let str = returnFirstValue([1, "hello", "mynameis"]);
// number | string

 

배열을 인수로 전달 (T[]) 하면 T는 배열의 요소 타입으로 할당된다. 
또, 요소의 타입이 다양하면 유니언 타입으로 추론한다. 

만약 유니언 타입이 아니라 첫번째 요소의 타입으로만 추론하고 싶다면 튜플타입으로 정의해주면 된다.

 

function returnFirstValue<T>(data: [T, ...unknown[]]) {
  return data[0];
}

let str = returnFirstValue([1, "hello", "mynameis"]);
// number

 

 

extends로 타입 변수 제한하기

function getLength<T extends { length: number }>(data: T) {
	return data.length;
}

// 가능한 것
let var1 = getLength([1, 2, 3]);
let var2 = getLength('12345');
let var3 = getLength({ length: 10 });

// 불가능한 것
let var4 = getLength(10);
let var5 = getLength(true);
let var6 = getLength(undefined);
let var7 = getLength(null);

 

T를 {length:number} 프로퍼티를 포함한 것으로 확장시켜서
변수 data를 적어도 number 타입의 프로퍼티 length 를 가지고 있는 것으로 제한시킨다.