프론트엔드/Typescript

[타입스크립트] 제네릭 - 1 (개념과 예제)

zzocco94 2023. 1. 12. 17:01

애니스크립트 대신 제네릭을 사용해보자

공통으로 사용할 커스텀 훅을 만들 때, 어떤 타입의 파라미터가 들어올 지 모르면
value : any 로 하곤 했었다 (No! 지양해야 됨)

 

이 때 팀원 분께서 해주신 코드 리뷰!
" any 대신 사용하는 곳에 따라서 type을 넣어줄 수 있는 generic을 사용해주세요 "

 

이름만 들어보고 어떤 역할을 하는지 몰랐었는데
타입스크립트 강의를 들어보니 정말 유용한 개념이라 글로 남기게 되었다.

 

제네릭이란?

개념

  • 단순하게 설명하면 '인자로 타입을 받는 함수'
// 인자로 들어온 것을 타입으로 삼는다 : "hi" => string

const log = <T extends {})(value: T) => {
    console.log(value);
    return value;
};

const res = log("hi");
  • extends는 타입을 제한해주는 역할을 한다고 보면 된다.
// T는 string | number[]의 부분집합인 셈

const foo = <T extends (string | number)[])(args: T) => {
	return args;
};

foo([1234, "asdfasdf"]);

 

장점

  • 한번의 선언으로 다양한 타입에 재사용이 가능하다
// before : 파라미터 타입별로 함수 여러개 선언

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

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

// after : 하나 선언해서 재사용
// 리턴 값은 타입 추론이 되나, 엄격하게 쓰고 싶을 경우 명시해도 ok

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

 

함수를 예로 들어 설명해보자면, 보통 함수를 선언할 때 파라미터 타입을 명시하는데
함수를 호출할 때 타입을 명시할 수 있다는 뜻!
예제를 통해 더 자세하게 알아보자

 

예제

1. 함수 파라미터 타입이 유니온일 때

  • 기존 : 파라미터가 string 또는 number라면, split 메소드를 쓸 수 없다
function logText(text: string | number): string | number {
    console.log(text);
    return text;
}

const str = logText('abc');
str.split(''); // error! number일 수도 있기 때문에 string 일 때만 쓸 수 있는 메소드는 사용 불가

 

  • 제네릭 : 함수를 호출할 때 타입을 명시하기 때문에, split 메소드를 사용할 수 있다.
function logText<T>(text: T): T {
    console.log(text);
    return text;
}

const str = logText<string>('abc');
str.split(''); // 가능

 

인터페이스에서 제네릭 선언하기

  • 드랍다운처럼 특정 프로퍼티의 값이 자주 바뀌는 편이라면, 인터페이스에서 제네릭을 선언할 수 있다
interface Dropdown<T> {
    value: T;
    selected: boolean;
}

const obj1: Dropdown<number> = { value: 1, selected: false };
const obj2: Dropdown<string> = { value: 'abc', selected: false };

 

예제 2 - 드롭다운

  • 기존 : value별로 인터페이스를 생성한다 (Email, ProductNumber)
interface Email {
    value: string; // 다름
    selected: boolean;
}

interface ProductNumber {
    value: number; // 다름
    selected: boolean;
}

const emails: Email[] = [
    { value: 'naver.com', selected: true },
    { value: 'gmail.com', selected: true },
];

const numberOfProducts: ProcuctNumber[] = [
    { value: 1, selected: true },
    { value: 2, selected: true },
];

function createDropdownItem(item: Email | ProcuctNumber) {
  const option = document.createElment('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 selecteTag = document.querySelector('#email-dropdown');
    selectTag.appendChild(item); 
});

 

  • 제네릭 : DropdownItem 재사용
  • Tip
    createDropdownItem(item : DropdownItem | DropdownItem) 유니온 타입을
    createDropdownItem<T>(item : DropdownItem<T>) 로 변경할 수 있다
interface DropdownItem<T> {
    value: T;
    selected: booelan;
}


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

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

function createDropdownItem<T>(item: DropdownItem<T>) {
  const option = document.createElment('option');
    option.value = item.value.toString();
    option.innerText = item.value.toString()
    option.selected = item.selected;
    return option;
}

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

 

출처 : 인프런 '타입스크립트 입문 - 기초부터 실전까지' 강의, 2023 원티드 프리온보딩 FE 1월 강의를 듣고 정리한 글입니다.