본문 바로가기
Javascript

타입스크립트 - 제네릭

by 안자바먹지 2021. 4. 8.
728x90

제네릭은 타입을 함수의 파라미터처럼 사용하는 것을 의미한다.

 

function getText(text) {
  return text;
}

getText('hi'); // 'hi'
getText(10); // 10
getText(true); // true

 

위 함수는 파라미터로 text를 받고 그 값을 바로 리턴하는 함수이다.

 

function getText<T> (text: T): T {
  return text;
}

// 첫 번째 사용 방법
getText<string>('hi'); // 'hi'
getText<number>(10); // 10
getText<boolean>(true); // true

// 두 번째 사용 방법
getText("Hello") // Hello

 

위 함수는 제네릭 기본 문법이 적용된 모습이다. 함수를 호출할 때 함수 안에서 사용할 타입( T )을 넘겨줄 수 있다. 또한 사용방법으로는 2가지로 나눌 수 있는데, 보통 두 번째 방법이 코드도 짧고 가독성이 좋기 때문에 많이 사용한다. 하지만 두 번째 방법으로도 타입 추정이 되지 않으면 첫 번째 방법을 사용한다.

 

 

기존 타입 정의와 제네릭의 차이

 

 

 

기존 타입 정의에서 유니온을 사용했을 경우, string과 number의 공통 속성에 대해서만 자동완성이 된다.

또한 해당 함수의 반환 값을 변수에 담아 확인해 보면 타입이 string | number 로 된것을 볼 수 있다.

 

하지만 제네릭을 사용했을 경우

 

 

이렇게 해당 함수의 반환 값을 변수에 담아 확인해 보면 제네릭으로 넘겨준 타입이 된 것을 볼 수 있다.

 

 

제네릭을 사용하지 않고 기본 타입을 썻을 경우

 

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

emails.forEach(email => {
  const item = createDropdownItem(email);
  const selectTag = document.querySelector('#email-dropdown');
  selectTag.appendChild(item);
});

numberOfProducts.forEach(product => {
  const item = createDropdownItem(product)
})

 

이렇게 하면 타입 정의에 대한 코드가 늘어난다. (Email과 ProductNumber의 interface)

 

 

제네릭을 사용한 경우

 

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(email => {
  const item = createDropdownItem(email);
  const selectTag = document.querySelector('#email-dropdown');
  selectTag.appendChild(item);
});

numberOfProducts.forEach(product => {
  const item = createDropdownItem(product)
  // ...
})

 

 

제네릭의 타입 제한

 

function logTextLength<T>(text:T[]): T[] {
  console.log(text.length)
  text.forEach(data => {
    console.log(data)
  })
  return text
}

logTextLength(['a', 'b', 'c'])

 

위 코드의 경우 제네릭의 타입이 배열이라고 정의한 것이다.

 

 

interface LengthType {
  length: number;
}

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

logTextLength('a') // 가능
logTextLength({length: 10}) // 가능
logTextLength({leng: 10}) // 불가능
logTextLength(10) // 불가능 (number에는 length속성이 없다.)

 

위 코드는 정의된 타입을 이용하여 제네릭의 타입을 제한할 수 있다.

제네릭이 LengthType의 인터페이스를 상속받았기 때문에 LengthType의 속성을 사용할 수 있다.

 

 

keyof로 제네릭 타입 제한하기

 

 

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

// ShoppingItem에 있는 key들 중에 한가지가 제네릭이 된다는 의미
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
  return itemOption
}

getShoppingItemOption('name') // 가능
getShoppingItemOption('price') // 가능
getShoppingItemOption('stock') // 가능
getShoppingItemOption(10) // 불가능
getShoppingItemOption<string>('abc') // 불가능
728x90

댓글