Typescript | 제네릭

신세원·2020년 12월 16일
2
post-thumbnail

# Generic?🤨

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

제네릭같은 경우 타입스크립트를 처음 시작할때 입문자,또는 처음 시작하는 사람들에게 굉장히 두려워 하는 문법 중에 하나이다.(본인 또한 그래서 쉽게 정리하고자 한다.)

그럼 제네릭이 무엇인지 알아보기 위해 간단한 예제를 보자


//일반적인 TS형식의 문법
function logText(text:string):string{
	return text;
}


//제네릭 문법
function logText<T>(text:T):T{
	return text;
}

위 처럼 <T>(type)을 마치 함수의 파라미터의 개념으로 받게 되는것이 바로 제네릭이다.

제네릭 기본 문법🔥

//일반적인 코드
function logText(text){ //타입을 지정해주지 않을경우 파라미터(인자)의 기본값은 text:any로 지정된다.
  console.log(text);
  return text;
}
logText(10); // 숫자 10
logText('안녕하세요'); //문자열 안녕하세요
logText(true); // 진위값 true
//위 함수의 인자의 타입을 따로 지정해주지 않았기에 string,number,boolean이 다 들어올 수 있다.

위와 같은 코드는 우리가 일상적으로 보는 코드이다.

그럼 이제 위와 같은 코드를 제네릭 코드로 바꿔보자.

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

위 코드를 해석하자면

logText 함수를 호출할때 이 함수의 파라미터(인자)의 타입이 무엇인지, 같이 지정하며 넘겨준다는 뜻이다.

그럼 이제 이 logText 함수의 파라미터(인자)의 타입은 하이 라고하는 문자열이 된다.

따라서 문자열을 그대로 들고와서 한바퀴 돈 다음 반환하는것 까지 문자열이 된다고 정의한다.

정리하자면, 호출하는 시점에 이 문자열이 되었건 숫자가 되었건 타입을 넘겨주는것이 제네릭이다.

또 제네릭에선 interface도 아래와 같이 정의할 수 있다.

interface Dropdown<T>{
  value:T;
  selected:boolean;
  
const obj : Dropdown<string> ={value:'abc',selected:false}// O
const obj : Dropdown<number> ={value:'abc',selected:false}// Error!! 

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

1. 함수 중복 선언의 단점

다음 아래와 같은 코드가 있다. 이 코드를 잘보고 기억하길 바란다.


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

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

 logText('안녕') //안녕
 logNumber(10) //10

보기만 하면 별 문제 없는 코드이다.

하지만 어느정도 코드에 대한 지식이 풍부한 사람과, 코드의 관점에서 봤을때, 유지보수 관점에서 봤을때는 정말 좋지않은 코드이다.

단순히 타입을 다르게 받기위해 중복된 코드들을 계속 생산해 나가는건 코드의 가독성 뿐만 아니라 유지보수 측면에서 좋지않다.

그렇다면 위와 같은 코드를 어떻게 해결하면 좋을까? 많은 사람들이 유니온 방식으로 풀어나가려고 하는데 유니온 방식에는 몇가지 문제가 있다.
위 코드를 유니온 방식과 비교해보며 제네릭에 대해 더 공부해보도록 하자.

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


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

 logText('안녕')//안녕
 logText(10)//10

이렇게 적게되면, logText에 문자열과 숫자를 적어도 오류가 발생되지 않는다.

하지만 이렇게 적으면 몇가지 문제가 있는데,

1. text의 인자 'string',number'의 공통으로 접근할 수 있는 속성 (ex. toLocaleString,toSrting,valueOf)만 가지고 올 수 있다.

2. 인풋에 대한 문제점은 없지만 반환점에 대한 문제점이 생기게 된다.

자세한건 아래 코드를 봐보자.

const result = logText('안녕') // const result : string | number
result.split('') // Error!! string | number 타입에서는 split이 제공되지 않습는다.

보면 알겠지만, 정확히 이 타입이 무엇인지 알 수 있어야만 우리가 쓰고 싶은 메소드를 쓸 수 있다.

정확한 타입이 정해지지 않으면 원하는 메소드가 제공되지 않는다.

그럼 이제 제네릭으로 문제점 해결을 해보도록 하자.

3. 제네릭의 장점과 타입 추론에서의 이점


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

logText<string>()//이제 logText라고 하는 함수는 'string'이라는 타입을 받아서 쓰겠다라고 호출 할 때 정의한다.

const str = logText<string>('abc'); // const str:string 
str.split('') // 이제 원하는 메소드 사용 가능!

const login = logText<boolean>(true) // 이렇게도 시용 가능!

이렇게 제네릭이란건 함수를 정의할때 타입을 비어놓은 상태에서 ‘타입에 무엇이 들어갈거다’ 라는 호출한 시점에 정의 한 것, 그리고 그 타입을 추론해서 최종 반환값까지 붙일 수 있는것이다.

제네릭 실전 예제 살펴보기

아래와 같은 드롭다운 예제가 있다.

그리고 이제 이 드롭다운에 관련된 HTML 코드와 TS코드가 있다.

HTML - 실전 예시


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

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

위 코드에서 중요한 부분은 emails,numberOfProducts 배열들을 돌려서 selectTag에 붙인다는 뜻이다.

그럼 이제 이 실제 코드의 타입을 하나씩 정해보며 제네릭을 쓰기 전과 쓰고 난 후의 차이점에 대해 알아보자.

1) 제네릭 사용전 -유니온 방식

interface Email{
 value:string,
 selected:boolean;
}

interface ProduceNumber{
 value:number;
 selected:boolean;
}

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

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

function createDropdownItem(item : Email | ProduceNumber) {
 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((product)=>{
 const item = createDropdownItem(product)
})

1) 제네릭 사용후


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

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

유니온 방식보다 코드량이 줄어들어 훨씬 깔끔해진걸 볼 수 있다!

이처럼 제네릭은 타입의 대한 이점을 확실히 가져간다.

profile
생각하는대로 살지 않으면, 사는대로 생각하게 된다.

0개의 댓글