본문 바로가기

JavaScript/Typescript

Typescript 입문 - 제네릭

 

제네릭

제네릭은 c#, java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다.

 

 

function logText<T>(text: T):T{
    console.log(text);
    return text;
}

logText<string>('hello');

 

 

 

 

마치 타입을 함수의 파라미터 개념으로 받아올때 활용하는 개념이 제네릭이라고 할 수 있다.

 

제네릭을 활용하는 이유 

 

 

 function logText(text: string){
     console.log(text);
     return text;
 }
 
 function logNumber(numbe: number){
     console.log(number);
     return number;
 }
 

같은 기능을 하더라도 타입을 지정해주기 위해서는 위와같이 같은 기능이지만 타입이 다른 함수를 두가지 설정해주거나 유니온 타입등을 정해줘야한다. 

 

 

 

function logText(text: string | number){
    console.log(text);
    // string과 number을 모두 만족하는 교집합 안에서만 자동완성을 제공한다.
    return text;
}

const a = logText('a');
a.split('');

문자열을 넣었음에도 불구하고 정확한 타입 추정이 되지 않아 위와같은 오류가 발생한다. 

 

 

 

function logText<T>(text: T):T{
    console.log(text);
    return text;
}

const abc = logText<string>('hello');
abc.split("");

 

 

type을 호출 시점에 정의함으로써 위와같이 자동입력의 도움을 받을 수 있다. 

 

 

제네릭 활용 

 

index.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>

 

 

index.ts

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);
});

 

위의 형태를 타입으로 지정해준다.

index.ts

const emails: {value: string; selected:boolean}[] = [
  { value: 'naver.com', selected: true },
  { value: 'gmail.com', selected: false },
  { value: 'hanmail.net', selected: false },
];

const numberOfProducts: {value: number; selected:boolean}[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false },
];

function createDropdownItem(item: {value: string; selected:boolean}) {
  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 item = createDropdownItem(product);
  const selectTag = document.querySelector('#product-dropdown');
})

 

해당 경우에는 function createDropdownItem이 string 타입으로 지정되어있어 product 쪽에는 활용을 못하는 상황이다 

 

 

이를 해결하기위해서는

index.ts

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 Product{
  value: number;
  selected: boolean;
}

const numberOfProducts: Product[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false },
];

function createDropdownItem(item: Email | Product) {
  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 item = createDropdownItem(product);
  const selectTag = document.querySelector('#product-dropdown');
})

인터페이스와 유니온 타입을 활용한 방식으로 해결 할수 있다.

 

하지만 위의 방식은 매번 emails와 numberOfProducts 외에도 다른 타입들이 추가가 되면 

매번 인터페이스로 지정을 해줘야한다. 이를 위해 제네릭을 활용한다. 

 

interface DropDown<T>{
  value: T;
  selected: boolean;
}

const emails: DropDown<string>[] = [
  { value: 'naver.com', selected: true },
  { value: 'gmail.com', selected: false },
  { value: 'hanmail.net', selected: false },
];


const numberOfProducts: DropDown<number>[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false },
];

function createDropdownItem(item: DropDown<string> | DropDown<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 item = createDropdownItem(product);
  const selectTag = document.querySelector('#product-dropdown');
})

 

 

유니온 타입까지 제거하기위해서는 

 

function을 다시 제네릭으로 받아서 호출시에 타입을 지정해준다. 

interface DropDown<T>{
  value: T;
  selected: boolean;
}

const emails: DropDown<string>[] = [
  { value: 'naver.com', selected: true },
  { value: 'gmail.com', selected: false },
  { value: 'hanmail.net', selected: false },
];


const numberOfProducts: DropDown<number>[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false },
];

function createDropdownItem<T>(item: DropDown<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);
});

numberOfProducts.forEach(function(product){
  const item = createDropdownItem<number>(product);
  const selectTag = document.querySelector('#product-dropdown');
})

 

 

 

제네릭의 타입제한

 

 

function logTextLength<T>(text: T): T{
    // 해당타입에서는 어떤 타입이 들어올지 알 수 없다. 
    console.log(text.length);
    return text;
}
logTextLength<string>('hi');

 

 

제네릭 부분에서 어떤 타입이 들어올 수 있는지 알 수 없기 때문에 text.length를 사용할때 오류가 발생한다 

 

이떄 []등을 제네릭 뒤에 넣어줘서 힌트를 줄 수 있다.

 

 

 

//제네릭의 타입 제한 
function logTextLength<T>(text: T[]): T[]{
    // 해당타입에서는 어떤 타입이 들어올지 알 수 없다.
    // 따라서 T[] 이런식으로 힌트를 줄 수 있다.
    console.log(text.length);
    text.forEach(function(text) {
        console.log(text);
    });
    return text;
}
logTextLength<string>(['hi', 'abc']);

 

 

또는 정의된타입을 이용해서 타입을 제한해 줄 수 있다. 

//타입제한 2 정의된 타입 이용하기
interface LengthType {
    length: number;
}

function logTextLength<T extends LengthType>(text: T): T{
    console.log(text.length);
    return text;
}

logTextLength('hi');

 

또는 keyof를 활용해서 타입을 제한해 줄수 도 있다. 

 

 

interface ShoppingItem{
    name:string;
    price:number;
    stock:number;
}

function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T):T{
    return itemOption;
}

getShoppingItemOption("price");

 

keyof를 활용하면  interface에 선언한 인자중 한가지만 파라미터로 받아올 수 있다.

 

 

 

 

 

 

 

 

 

 

function getValue<T, O extends keyof T>(obj: T, key: O){
    return obj[key];
}

let objs = {a: 1, b: 2, c: 3};

getValue(objs, "b");

 

 

위와같은 방법으로도 활용이 가능하다.

 

 

 

 

 

 

www.inflearn.com/course/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%85%EB%AC%B8

해당 강의를 듣고 정리하는 내용입니다.