제네릭 사전적 정의
- 제네릭은 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 |
---|
- 제네릭 사전적 정의
- 제네릭을 사용하는 이유
- 기존 타입 정의 방식과 제네릭의 차이점 - 함수 중복 선언의 단점
- 기존 문법과 제네릭의 차이점 - 유니온 타입을 이용한 선언 방식의 문제점
- 제네릭의 장점과 타입 추론에서의 이점
- 제네릭 실전 예제
- 제네릭의 타입 제한
- 정의된 타입으로 타입을 제한하기
- keyof로 제네릭의 타입 제한하기