해당 게시글은
강의 사이트, 인프런에서 이정환님이 진행하시는
'한 입 크기로 잘라먹는 타입스크립트' 를
들어보며 블로그를 작성하려고 합니다.
다음 게시글 내용은 해당 강의에 핸드북의 내용으로
출처는 다음과 같습니다.
https://ts.winterlood.com/7250edd7-a3fd-4662-b756-f11f927c73f2
타입스크립트를 소개합니다 - 타입스크립트 개론
한 입 크기로 잘라먹는 타입스크립트
ts.winterlood.com
section7/chapter0.ts 에서 실습을 진행했습니다.
제너릭이란?
제너릭이란
함수나 인터페이스, 타입 별칭, 클래스 등을
다양한 타입과 함께 동작하도록 만들어 주는
타입스크립트의 놀라운 기능 중 하나입니다.
제너릭이 필요한 상황
다음과 같이 다양한 타입의 매개변수를 받고
해당 매개변수를 그대로 반환하는 함수가 하나 필요하다고 가정하겠습니다.
function func(value: any) {
return value;
}
let num = func(10);
// any 타입
let str = func("string");
// any 타입
다양한 타입의 매개변수를 제공받아야 하기 때문에
매개변수 value 의 타입을 일단 any 타입으로 해두었습니다.
물론 unknown 으로 정의해도 괜찮습니다.
이 함수는 인수로 전달한 값을 그냥 그대로 반환하는 함수입니다.
따라서 변수 num 에는 10이 저장되고,
변수 str 에는 "string" 이 저장됩니다.
그런데 현재 num 과 str 의 타입은 any 타입이 됩니다.
func 함수의 반환값 타입이 return 문을 기준으로 추론되었기 때문입니다.
이렇게 함수 호출 결과를 저장하는 num, str 등의 변수가
any 타입으로 추론되면
다음과 같은 문제저밍 발생합니다.
function func(value: any) {
return value;
}
let num = func(10);
let str = func("string");
num.toUpperCase()
num 에는 분명 Number 타입의 값, 10이 저장되어 있을 것이 분명합니다.
그러나 any 타입으로 추론되어버렸기 때문에
toUpperCase 등의 String 타입의 메서드를 사용해도
타입스크립트가 오류를 감지하지 못합니다.
이 코드는 결국 실제로 실행하면 런타임 오류를 발생시키는 아주 위험한 상태가 됩니다.
자 그러면 이번에는 매개변수의 타입을 any 타입 말고
unknown 타입으로 정의해보겠습니다.
function func(value: unknown) {
return value;
}
let num = func(10);
// unknown 타입
let str = func("string");
// unknown 타입
num.toUpperCase(); // ❌
num.toFixed(); // ❌
그러면 toUpperCase 같은 메서드 호출은 방지할 수 있습니다.
그러나 이번에는 또 다른 문제가 발생합니다.
toFixed 같은 Number 타입의 메서드 호출도 함께 오류로 판단하게 됩니다.
따라서 num 이 10이 저장될 것이 분명한데도
이 값을 사용하려면
다음과 같이 비효율적으로 타입 좁히기를 이용해야 합니다.
function func(value: unknown) {
return value;
}
let num = func(10);
// unknown 타입
let str = func("string");
// unknown 타입
if (typeof num === "number") {
num.toFixed();
}
사실 원하는 건 꽤나 간단합니다.
그냥 인수로 Number 타입의 갑을 전달하면, 반환 타입이 Number 가 되고,
인수로 Strung 타입의 값을 전달하면, 반환값의 타입도 String 타입이 되었으면 좋겠습니다.
그런데 지금까지 배운 타입스크립트 문법으로는 이 문제를 해결할 수 없습니다.
이럴 때 바로 제네릭을 이용해야 합니다.
func 함수를 제네릭 함수로 만들면 이 문제를 간단히 해결할 수 있습니다.
제네릭(Generic) 함수
제네릭(Generic) 함수란 무엇일까요?
일단 제네릭이란 일반적인 또는 포괄적인 이라는 뜻을 갖고 있습니다.
따라서 제네릭 함수는 일반적인 함수 또는 포괄적인 함수 정도로 해석할 수 있습니다.
무슨 말일까요?
제네릭과 그 의미가 거의 비슷한 단어가 하나 있습니다.
"제네럴(General)" 입니다.
제네럴은 영어권에서 종합병원을 이야기 할 때 사용되는 언어입니다.
영어권에서는 모든 병을 두루두루 포괄적으로 다루는 종합 병원을
제네럴 호스피탈(General Hospital)이라 부릅니다.
자 그러면 제네럴과 제네릭은 두루두루 다 적용될 수 있는, 또 포괄적인 이런 뜻으로 의역할 수 있습니다.
따라서 "제네릭 함수는 두루두루 모든 타입의 값을 적용할 수 있는 그런 범용적인 함수이다"
정도로 이해할 수 있습니다.
다음과 같이 제네릭 함수를 선언할 수 있습니다.
function func<T>(value: T): T {
return value;
}
let num = func(10);
// number 타입
함수 이름 뒤에 꺽쇠를 열고 타입을 담는 변수인 타입 변수 T 를 선언합니다.
그리고 매개변수와 반환값의 타입을
이 타입변수 T 로 설정합니다.
T에 어떤 타입이 할당될 지는 함수가 호출될 때 결정됩니다.
func(10) 처럼 Number 타입의 값을 인수로 전달하면
매개변수 value 에 Number 타입의 값이 저장되면서
T 가 Number 타입으로 추론됩니다.
그럼 이때의 func 함수의 반환값 타입 또한 Number 타입이 됩니다.
제네릭 함수를 호출할 때,
다음과 같이 타입 변수에
할당할 타입을 직접 명시하는 것도 가능합니다.
function func<T>(value: T): T {
return value;
}
let arr = func<[number, number, number]>([1, 2, 3]);
위 코드의 흐름은 다음과 같습니다.
1. T에 [Number, Number, Number] 튜플 타입이 할당됨
2. 매개변수 value 와 반환값 타입이 모두 튜플 타입이 됨
만약 위 코드에서 타입 변수에 할당할 타입을 튜플 타입으로 설정하지 않았다면
T가 number[ ] 타입으로 추론되었을 것입니다.
타입스크립트는 타입을 추론할 때 항상 일반적이고
좀 더 범용적인 타입으로 추론하기 때문입니다.
이렇듯 타입 변수에 할당하고 싶은 특정 타입이 존재한다면
함수 호출과 함께 꺽쇠를 열고 직접 명시해주는 게 좋습니다.
그렇지 않은 대다수의 상황에서는 알아서 잘 추론되기 때문에
굳이 타입 변수를 설정하지 않아도 됩니다.
'TypeScript > 한 입 크기로 잘라먹는 타입스크립트' 카테고리의 다른 글
[제네릭] map, forEach 메서드 타입 정의하기 (0) | 2023.07.16 |
---|---|
[제네릭] 타입 변수 응용하기 (0) | 2023.07.16 |
[클래스] 인터페이스와 클래스 (0) | 2023.07.15 |
[클래스] 접근 제어자 (0) | 2023.07.15 |
[클래스] 타입스크립트의 클래스 (0) | 2023.07.15 |