-
Notifications
You must be signed in to change notification settings - Fork 5
9. 제네릭(Generics)
Woo-Dong93 edited this page Nov 23, 2020
·
1 revision
- 제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다.
- 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용됩니다.
- 기존 자바스크립트 함수
function logText(text) {
console.log(text);
return text;
}
//타입을 정의하지 않아 어떤 값이든 인자로 넘길 수 있습니다.
logText(10); // 숫자 : 10
logText('하이'); // 문자열 : 하이
logText(true); // 진위값 : true
- 제네릭 활용
- 인자에 넣지 않으면 에러가 발생합니다.
- 파라미터의 타입을 지정하면서 인자로 넘겨줄 수 있습니다.
- 즉 호출하는 시점에 타입을 넘겨줄 수 있는 것이 제네릭 입니다.
function logText<T>(text: T):T {
console.log(text);
return text;
}
logText_2('하이');
//직접 넘겨주는 타입도 적어줄 수 있습니다.
logText_2<string>('하이');
- 기존 함수 선언
- 암묵적으로 타입이 any로 되어있습니다.
- 타입을 여러개 정의해야할 경우 여러개의 중복 함수를 생성해야 합니다.
function logTexts(text) {
console.log(text);
return text;
}
logTexts('a');
logTexts(10);
logTexts(true);
function logTexts_2(text: string) {
console.log(text);
text.split('').reverse().join('');
return text;
}
// 문자열로 타입을 정의했으니 다른 것들은 못받습니다.
logTexts_2('a');
logTexts_2(10); // 에러
logTexts_2(true); // 에러
// 숫자를 받기 위해 중복 함수를 생성해야 합니다.
function logNumber_2(num: number) {
console.log(num);
return num;
}
logNumber_2(10);
-
단순히 타입을 다르게 받기 위해서 중복되는 코드들을 계속적으로 생성하는 것은 가독성이나 유지보수 측면에서 안좋은 코딩이 됩니다.
-
이런 것들을 해결하기 위한 것이 유니온 타입입니다.
-
하지만 유니온 타입도 문제점이 있습니다.
- 여러개의 타입을 받을 수 있지만 타입이 명확하지 않습니다.
function logText(text: string | number) {
console.log(text);
text. //(string과 number가 공통으로 접근사용할 수 있는 속성이나 api만 프리뷰로 자동완성하게 됩니다.)
return text;
}
const a = logText('a');
a. //여기도 여전히 string or number로 취급받아 공통으로 사용가능한 속성과 api만 자동완성 됩니다.
a.split(''); //에러발생, 타입이 string이 명확해야 사용가능 하기 때문에 split을 사용할 수 없게 됩니다.
logText_3(10);
- 제네릭의 장점과 타입 추론의 이점
- 동일한 함수에 대해서 타입을 유동적으로 사용 가능합니다.
- 여러개의 타입을 받아도 타입을 명확하게 할 수 있습니다.
function logText<T>(text: T): T {
console.log(text);
return text;
}
const str = logText<string>('hi');
str.split(''); //에러없이 사용 가능, 타입이 명확해지기 때문입니다.
//동일한 함수에 대해서 타입을 유동적으로 사용 가능합니다.
const login = logText<boolean>(true);
- 제네릭을 사용하기전 Select에 Option을 추가하는 코드
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;
}
// 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
- 일반 타입스크립트를 활용
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 },
];
// numberOfProducts을 넣을 때에는 value: number로 바꿔줘야 합니다..
// 즉 두개를 모두 수용할 수 있는 타입을 정의해줘야 합니다.
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;
}
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (product){
const itme = createDropdownItem(product);
})
- 인터페이스에서 제네릭을 활용해보기
interface Dropdown {
value: string;
selected: boolean;
}
// 에러가 발생합니다. value에는 number가 들어갈 수 없으니...
const obj: Dropdown = { value: 10, selected: false };
interface Dropdown_2<T> {
value: T;
selected: boolean;
}
// 인터페이스에서 제네릭 활용하기
const obj_2: Dropdown_2<number> = { value: 10, selected: false };
- 제네릭 실전 예제 적용해보기
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;
}
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (product){
const itme = createDropdownItem(product);
})
- 유니온 타입 제거하기
function createDropdownItem<T>(item: DropdownItem<T>) {
const option = document.createElement('option');
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
emails.forEach(function (email) {
const item = createDropdownItem<string>(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
- 제네릭에 특정 타입을 부여 하여 힌트를 제공하게 합니다.
function logTextLength<T>(text: T[]): T[] {
console.log(text.length); // T는 배열이기 때문에 length를 제공하므로 오류가 발생하지 않습니다.
text.forEach // 배열이니 forEach도 제공합니다.
return text;
}
logTextLength<string>('hi'); // 오류 발생 : 배열을 인자로 받고 있습니다.
logTextLength<string>(['hi']); // 가능
- 정의된 타입으로 타입 제한 ( extends )
- 타입에서 제공되는 속성을 이미 정의할 수 있습니다.
interface LengthType {
length: number;
}
function logTextLength<T extends LengthType>(text: T): T {
text.length; // 오류가 발생하지 않습니다.
return text;
}
logTextLength('a'); // 가능 : 문자열은 length속성을 제공하므로 가능합니다.
logTextLength({length: 10}); // 가능 : length속성을 가진 객체도 들어 갈 수 있습니다.
logTextLength(10); // 오류 발생 : 기본적으로 숫자 10은 length라는 속성이 존재하지 않기 때문입니다.
- keyof로 제네릭 타입 제한
- 인터페이스에서 정의한 타입의 key값 중 한가지로 타입을 제한할 수 있습니다.
- key들 중에 한가지가 바로 제네릭(타입)이 될 것이다라는 것을 의미합니다.
interface ShoppingItem {
name: string;
price: number;
stock: number;
}
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
return itemOption;
}
// 에러발생
getShoppingItemOption(10);
getShoppingItemOption<string>('a');
// 가능
getShoppingItemOption('name');
getShoppingItemOption('price');