제네릭 사전적 정의
- 제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징
- 특히, 한 가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는 데 사용
제네릭 기본 문법이 적용된 형태
function logText<T>(text: T): T {
return text;
}
- 제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미
logText<string>('hi');
logText<number>(10);
logText<boolean>(true);
- 함수를 호출할 때 파라미터의 대한 타입을 인자값에 지정해서 호출한다.
- getText<string>('hi');
getText 함수는 아래와 같이 타입을 정의한 것과 같다.
function getText<string>(text: string): string {
return text;
}
- 위 함수는 입력 값의 타입이 string이면서 반환 값 타입도 string이어야 한다
제네릭을 사용하는 이유
function logText(text: any): any {
return text;
}
- 위와 같이 any를 사용할 수 있지만 함수의 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지는 알 수가 없다. → any라는 타입은 타입 검사를 하지 않기 때문
function logText<T>(text: T): T {
return text;
}
- 함수의 인자와 반환 값에 모두 T라는 타입을 추가
- 함수를 호출할 때 넘긴 타입에 대해 타입스크립트가 추정할 수 있게 된다.
- 함수의 입력 값에 대한 타입과 출력 값에 대한 타입이 동일한지 검증할 수 있게 된다.
// #1
const text = logText<string>("Hello Generic");
// #2
const text = logText("Hello Generic");
- 보통 두 번째 방법이 코드도 더 짧고 가독성이 좋기 때문에 흔하게 사용
- 만약 복잡한 코드에서 두 번째 코드로 타입 추정이 되지 않는다면 첫 번째 방법을 사용하기
기존 타입 정의 방식과 제네릭의 차이점 - 함수 중복 선언의 단점
function logText(text: string) {
return text;
}
function logNumber(num: number) {
return num;
}
- 위와 같이 타입만 다른데 함수를 만드는 것은 좋지 않다. 제네릭 방식으로 처리하자.
기존 문법과 제네릭의 차이점 - 유니온 타입을 이용한 선언 방식의 문제점
유니온 타입을 이용한 선언 방식
function logText(text: string | number {
return text;
}
문제점 (반환값에 대해 문제가 생긴다.)
- string과 number가 공통으로 접근할 수 있는 속성이나 API만 사용이 가능해진다.
제네릭의 장점과 타입 추론에서의 이점
유니온 타입을 이용한 선언 방식 대신 제네릭으로 사용
function logText<T>(text: T): T {
return text;
}
const str = logText<string>('abc');
str.split(''); // string의 속성을 사용할 수 있음
const login = logText<boolean>(true); // // 타입의 이점
- 호출하는 시점에서 타입을 정할 수 있다.
- 타입을 추론해서 반환값까지 붙일 수 있다.
제네릭 실전 예제
예제 HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<h1>이메일 선택 드롭다운</h1>
<select id="email-dropdown">
<option value="naver.com" selected>naver.com</option>
<option value="google.com">google.com</option>
<option value="hanmail.net">hanmail.net</option>
</select>
</div>
<div>
<h1>상품 수량 선택 드롭다운</h1>
<select id="product-dropdown">
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
</body>
</html>
1) 기본
const emails = [
{ value: 'naver.com', selected: true },
{ value: 'gmail.com', selected: false },
{ value: 'hanmail.net', selected: false },
];
const numberOfProducts = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false },
];
function createDropdownItem(item) {
const option = document.createElement('option');
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
2) 코드에 타입 정의
interface Email {
value: string;
selected: boolean;
}
const emails: Email[] = [
{ value: "naver.com", selected: true },
{ value: "gmail.com", selected: false },
{ value: "hanmail.net", selected: false }
];
interface ProductNumber {
value: number;
selected: boolean;
}
const numberOfProducts: ProductNumber[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false }
];
function createDropdownItem(item: Email | ProductNumber) {
const option = document.createElement("option");
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector("#email-dropdown");
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (Product) {
const item = createDropdownItem(Product);
});
2) 인터페이스에 제네릭을 선언하는 방법 (2번의 타입 정의를 제네릭으로 선언하기)
interface DropdownItem<T> {
value: T;
selected: boolean;
}
const emails: DropdownItem<string>[] = [
{ value: "naver.com", selected: true },
{ value: "gmail.com", selected: false },
{ value: "hanmail.net", selected: false }
];
const numberOfProducts: DropdownItem<number>[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false }
];
function createDropdownItem(item: DropdownItem<string> | DropdownItem<number>) {
const option = document.createElement("option");
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector("#email-dropdown");
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (Product) {
const item = createDropdownItem(Product);
});
2-1) 제네릭의 타입을 문자와 숫자만 받게끔 유니온 타입으로 제한
interface DropdownItem<T> {
value: T;
selected: boolean;
}
const emails: DropdownItem<string>[] = [
{ value: "naver.com", selected: true },
{ value: "gmail.com", selected: false },
{ value: "hanmail.net", selected: false }
];
const numberOfProducts: DropdownItem<number>[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false }
];
function createDropdownItem<T extends string | number>(item: DropdownItem<T>) {
const option = document.createElement("option");
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem<string>(email);
const selectTag = document.querySelector("#email-dropdown");
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (Product) {
const item = createDropdownItem<number>(Product);
});
2-2) 제네릭의 타입을 toString()이라는 메서드를 가진 타입으로 제한
interface DropdownItem<T> {
value: T;
selected: boolean;
}
const emails: DropdownItem<string>[] = [
{ value: "naver.com", selected: true },
{ value: "gmail.com", selected: false },
{ value: "hanmail.net", selected: false }
];
const numberOfProducts: DropdownItem<number>[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false }
];
function createDropdownItem<T extends { toString: Function }>(
item: DropdownItem<T>
): HTMLOptionElement {
const option = document.createElement("option");
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem<string>(email);
const selectTag = document.querySelector("#email-dropdown");
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (Product) {
const item = createDropdownItem<number>(Product);
});
- 제네릭으로 받을 수 있는 타입을. toString() 함수가 있는 타입으로 제한하는 것
- toString() API는 객체의 내장 API이다.
- 해당 객체를 프로토 타입으로 받고 있는 문자, 숫자, 불린 등 주요 타입들에도 다 toString() API가 기본적으로 제공된다.
- 2-1번 코드보다는 문자, 숫자 이외에 더 많은 타입을 수용할 수 있는 형태가 된다.
제네릭의 타입 제한
logTextLength 함수는 제네릭 타입이다.
여기서 text.length 는 사용할 수가 없다.
함수 호출 시 타입이 정해지기 때문에 미리 문자가 올 것이라 추론하지 못하기 때문이다.
function logTextLength<T>(text: T[]): T[] {
console.log(text.length); // 2
text.forEach((text) => {
console.log(text);
});
return text;
}
logTextLength<string>(["hello", "hi"]);
제네릭을 선언하고 length를 사용하려면 배열로 만들어줘야 한다.
여기서의 length는 배열의 개수이며 글자의 길이가 아니다.
글자의 length를 사용할 때 아래처럼 해주었다.
function logTextLength<T>(text: T): T {
console.log(text)
return text;
}
const logText = logTextLength<string>("hello");
console.log(logText.length)
정의된 타입으로 타입을 제한하기
제네릭을 사용한 함수 안에 length 사용하는 방법은 인터페이스를 사용해 주기.
interface LengthType {
length: number;
}
function logTextLength2<T extends LengthType>(text: T): T {
text.length;
return text; // 반환값에 text.length를 사용할 수 없다.
}
logTextLength2("abc"); // 사용가능
logTextLength2(10); // 사용 불가능 - 숫자에는 length 속성이 없다.
logTextLength2({ length : 20}); // 사용가능
keyof로 제네릭의 타입 제한하기
interface ShoppingItems {
name: string;
price: number;
address: string;
stock: number;
}
function getAllowedOptions<T extends keyof ShoppingItems>(option: T): T {
return option;
}
getAllowedOptions(10); // X
getAllowedOptions<string>('a'); // X
getAllowedOptions('name'); // O | ShoppingItems 인터페이스의 key 값만 사용이 가능
'Frontend > TypeScript' 카테고리의 다른 글
타입 별칭과 인터페이스 차이점 (0) | 2023.03.22 |
---|