제네릭이란
타입스크립트에서 제네릭이란 함수나 인터페이스, 타입 별칭, 클레스 등을 다양한 타입과 함께 동작하도록 만들어 주는 기능을 말합니다.
제네릭(Generic)이란 일반적인 또는 포괄적인 이라는 뜻을 가지고 있습니다. 제네릭과 의미가 비슷한 단어는 제네럴(General)입니다. 제네럴은 영어권에서 종합병원을 이야기 할 때 사용되는 단어로, 두루두루 다 적용할 수 있는 포괄적인 뜻으로 생각할 수 있습니다.
제네릭이 필요한 상황
function example(value:any){
return value
};
let numberValue = example(123); // any 타입
let stringValue = example('Korea'); // any 타입
example
함수는 인수로 전달한 값을 그대로 반환하는 함수입니다. 따라서 변수 numberValue
에는 123이 저장되고, 변수 stringValue
에는 'Korea'이 저장됩니다. 여기서 타입스크립트는 numberValue
와 stringValue
변수의 타입을 각각 number, string이 아닌 any타입으로 추정하게 됩니다. 이는 example
함수의 반환값 타입을 return 문을 기준으로 추론하기 때문입니다.
이렇게 함수 호출 결과를 저장하는 numberValue
, stringValue
등의 변수가 any 타입으로 추론되면 아래와 같은 문제가 발생하게 됩니다.
numberValue
에는 number 타입의 값 123이 저장되어 있지만, any 타입으로 추론되었기 때문에 string타입의 메서드인 toUpperCase
를 사용해도 오류를 감지하지 못합니다. 이러한 코드를 실제로 실행하면 런타임 오류를 발생시키게 됩니다.
이런 경우에 제네릭을 사용하면 문제를 해결할 수 있습니다.
제네릭 함수
제네릭 함수는 다음과 같이 선언할 수 있습니다.
function example<T>(value:T):T {
return value
};
let numberValue = example(123); // number 타입
let stringValue = example('Korea'); // string 타입
함수 이름 뒤에 꺽쇠(<>)를 열고 타입을 담는 변수인 타입 변수 T를 선언한 후, 매개변수와 반환 값의 타입을 이 타입변수 T로 설정합니다.
T에 어떤 타입이 할당될 지는 함수가 호출될 때 결정됩니다. example(123)
처럼 number 타입의 값을 인수로 전달하게 되면 매개변수 value에 number 타입의 값이 저장되면서 타입스크립트는 T를 number 타입으로 추론하게 됩니다. 그럼 이때의 example
함수의 반환 값 타입 또한 number 타입이 됩니다.
제네릭을 사용하게 되면 변수의 타입이 any로 추론되는 것을 방지할 수 있으며 잘못된 메서드 사용을 찾아내는 것을 볼 수 있습니다.
제네릭 함수를 호출할 때 다음과 같이 타입 변수에 할당할 타입을 직접 명시하는 것도 가능합니다.
function func<T>(value: T): T {
return value;
}
let arr = func<[number, number, number]>([1, 2, 3]);
만약 위 코드에서 타입 변수에 할당할 타입을 튜플 타입으로 설정하지 않았다면 T가 number[]
타입으로 추론되었을 것입니다. (타입스크립트는 타입을 추론할 때 항상 일방적이로 범용적인 타입으로 추론하기 때문) 타입 변수에 할당하고 싶은 특정 타입이 존재한다면 함수 호출과 함께 직접 명시해주는 것이 좋습니다.
응용하기
사례1 - 2개의 타입 변수
function exam<T, U>(a: T, b: U) {
return [b, a];
}
const [a, b] = exam("1", 2);
사례2 - 다양한 배열 타입을 인수로 받는 제네릭 함수
function returnFirstValue<T>(data: T[]) {
return data[0];
}
let num = returnFirstValue([0, 1, 2]);
// number
let str = returnFirstValue([1, "hello", "mynameis"]);
// number | string
returnFirstValue함수를 처음 호출했을 때는 인수로 number[] 타입의 값을 전달했기 때문에 T는 number 타입으로 추론됩니다. 반면에 두번째로 호출 했을 때는 인수로 (number | string)[]타입의 값을 전달했기 때문에 T는 number | string 타입으로 추론됩니다.
만약 반환값의 타입을 배열의 첫번째 요소의 타입이 되도록 하고 싶다면, 다음과 같이 튜플 타입과 나머지 파라미터를 이용하면 됩니다.
function returnFirstValue<T>(data: [T, ...unknown[]]) {
return data[0];
}
let str = returnFirstValue([1, "hello", "mynameis"]);
// number
사례3 - 타입 변수를 제한
function getLength<T extends { length: number }>(data: T) {
return data.length;
}
getLength("123"); // 👍🏻
getLength([1, 2, 3]); // 👍🏻
getLength({ length: 1 }); // 👍🏻
getLength(100); // ❌
getLength(undefined); // ❌
getLength(null); // ❌
타입 변수를 제한할 때는 extends(확장)를 이용합니다.
위와 같이 T extends { length: number }
라고 정의하면 T는 number 타입의 프로퍼티 length를 가지고 있는 타입이어야 합니다. 때문에 number, undefined, null과 같이 프로퍼티로 length를 가지고 있지 않는 타입은 오류를 발생하게 됩니다.
'TypeScript' 카테고리의 다른 글
컴파일러 옵션 (0) | 2023.08.01 |
---|