JSTS

[TS] 5. 제네릭

Jaaaay 2022. 7. 7. 17:43

제네릭(Generics)

제네릭은 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다. 특히 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용된다.

제네릭은 타입을 마치 함수의 파라미터의 개념으로 사용한다.

제네릭의 기본 문법

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

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

  • 함수 중복 선언의 단점
function logText(text: string) {
  console.log(text);
  return text;
}

function logNumber(num: number) {
  console.log(num);
  return num;
}

logText('a');
logText(10); // 오류
const num = logNumber(10);
logText(true); // 오류

→ 유지 보수 측면에서 좋지 않음

  • 유니온 타입을 이용한 선언 방식의 문제점

'string | number' 형식에 'split' 속성이 없습니다.

'number' 형식에 'split' 속성이 없습니다.ts(2339)

→ a에 대해서 다음과 같은 오류가 뜬다. a는 string | number 타입으로 정의되어 있다.

  • 제네릭의 장점과 타입 추론에서의 이점
function logText<T>(text: T): T{
  console.log(text);
  return text;
}

const str = logText<string>("hi");
str.split("");
const login = logText<boolean>(true);

→ 제네릭을 통한 타입 추정이 가능


인터페이스를 이용한 타입 정의 예제

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

제네릭을 이용한 타입 정의

  • 인터페이스에 제네릭을 선언하는 방법
interface Dropdown<T> {
  value: T;
  selected: boolean;
}

const obj: Dropdown<string> = {value: 'abc', selected: false};
obj.value.split('')
  • 제네릭을 이용한 타입 정의 예제
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>(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: DropdownItem<string>) {
  const item = createDropdownItem<string>(email);
  const selectTag = document.querySelector("#email-dropdown");
  selectTag.appendChild(item);
});

numberOfProducts.forEach(function (product: DropdownItem<number>) {
  const item = createDropdownItem<number>(product);
});

제네릭의 타입 제한

  • 제네릭의 타입 제한 1
function logTextLength<T>(text: T[]): T[]{
  console.log(text.length);
  text.length
  text.forEach(function(text){
    console.log(text);
  })
  return text;
}

logTextLength<string>(['hi','abc']);

함수 내에서 length 속성이 있는지 알기 위해서는 배열 요소라는 것을 타입스크립트에 알려준다.

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

// 정의된 타입을 이용해 제네릭의 타입을 제한 (extends)
function logTextLength<T extends LengthType>(text: T): T {
  text.length;
  return text;
}
logTextLength("a"); // 기본적으로 문자열에서 length가 제공되기 때문에 ok
logTextLength(10); // length가 제공되지 않으므로 오류
logTextLength({ length: 10 }); // 객체라도 length가 있으므로 ok
logTextLength({ leng: 10 }); // length가 없으므로 오류
  • 제네릭의 타입 제한 3 - keyof
interface ShoppingItem {
  name: string;
  price: number;
  stock: number;
}

// T를 타입으로 가지는 변수에 들어갈 값을 제한
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
  return itemOption;
}

// getShoppingItemOption<number>(10);
// getShoppingItemOption<string>('a');
getShoppingItemOption("name");