Skip to content

9. 제네릭(Generics)

Woo-Dong93 edited this page Nov 23, 2020 · 1 revision

  • 제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다.
  • 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용됩니다.

1. 제네릭의 기본문법

  • 기존 자바스크립트 함수
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>('하이');

2. 기존 타입 정의 방식과 제네릭의 차이점

  • 기존 함수 선언
    • 암묵적으로 타입이 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);

3. 제네릭 실전 예제

  • 제네릭을 사용하기전 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);
});

4. 제네릭의 타입 제한

  • 제네릭에 특정 타입을 부여 하여 힌트를 제공하게 합니다.
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');

💒 Home

Home

📆 Planning

📋 요구 사항

📑 프로젝트 설계

📓 Api 명세서

📖 제품 백로그

📺 화면 기획서

📽️ Project

📖 도움말

📷 실행 화면

⚒️ 기술 스택

⚙️ 기술 특장점

✔️ Team Rule

그라운드 룰

☑️ 깃허브 사용 규칙

코딩 컨벤션 규칙

📝 Progress

🌿 1주차 Progress
☘️ 2주차 Progress
🍀 3주차 Progress
🍁 4주차 Progress
🌲 5주차 Progress

📚 학습 정리 공유

🛠️ 기술 관련 공유

Clone this wiki locally