본문 바로가기

Typescript

[typescript 기본] 제네릭 문법

제네릭을 쓰는 이유

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

const number = logText(10);
console.log(number + 1);
logText("하이");
logText(true);
매개변수로 숫자, 문자, boolean 중 어떤걸 보낼지 모르고 보낸 값으로 나중에 뭔가 다른 처리를 하려고 할때 정의하기가 애매하다. 이럴때 쓰는게 제네릭 문법이다.

 

제네릭을 쓰는 이유2 - uniontype으로도 불가

function textLog(text: string) {
  console.log(text)
  text.split(""); //예시
  return text;
}

function numberLog(num: number) {
  console.log(num)
  num = num + 1;
  return num;
}
만약 숫자랑 문자이냐에 따라서 다른 행동을 하고 싶다고 했을때 이를 위해 함수를 2개 만드는것은 좋지않은 코드가 된다. 위를 해결하기 위해서 union type을 사용하여 해결은 가능하지만 받는 곳에서 number인지 string인지 알지 못하기 때문에 오류가 발생한다. (아래코드 참고)

 


제네릭이란?

정해져있는 타입으로 받는게 아닌 보낼때 타입을 지정하는 것!!

 


제네릭 기본

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

logText<string>("하이");
보면 함수옆, 매개변수 옆, 괄호 옆에 T가 있다.

함수옆에 있는 <T> 로 보내는 타입값을 받아서 뒤에 매개변수와 리턴값(괄호옆)의 타입을 지정해준다.
밑에 예시처럼 string으로 타입을 지정해서 보내면 매개변수 text는 string이 되고, return값도 string이 된다.

 


interface에 제네릭 문법 사용하기

interface Dropdown<T> {
    value: T;
    selected: boolean;
}

const obj: Dropdown<string> = {value: "abc", selected: false}
const obj2: Dropdown<number> = { value: 1, selected: false };
const obj2: Dropdown<number> = { value: "abc", selected: false }; //오류

 


제네릭의장점 1 - 받을때 타입 정해서 다양하게 사용하기

// logText<T> //여기서의 T는 받은 타입
// (text:T) //받은 인자도 이 타입으로 쓰겠다
// :T //return 되는 값도 이 타입으로 쓰겠다
// 호출하는 시점에서 정해지기 때문에 어떤 타입이든 활용 가능 !!

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


//호출할때 string으로 정의
const str = logText<string>("abc");
str.split("");

//호출할 때 boolean 으로 정의
const isLogin = logText<boolean>(true);
if (isLogin) {}
else {}
위에서 "제네릭을 쓰는 이유2 - uniontype으로도 불가"를 제네릭문법으로 해결한 코드이다.
보낼때 string으로 타입을 지정해서 보내서 return 값을 string으로 받아 split으로 처리하고, boolean타입으로 지정하여 if 문 등에 사용할 수 있다.

 


제네릭의장점 2 - 하나의 인터페이스로 여러개의 타입을 커버하기

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 },
];
위의 코드를 보면 emails와 numberOfProducts는 array이고, 안에 객체는 value와 selected로 이루어져있는 것을 알 수 있다.
하지만 email은 value가 string이고, numberOfProducts는 number이기 때문에 제네릭을 사용하지 않는다면 2개의 interface를 만들어야한다. (아래코드 참고)

 

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

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

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

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

interface를 2개를 만든 모습

 

아래는 제네릭문법을 사용하여 짠 코드이다. 하나의 인터페이스로 여러개의 타입을 커버하는 것을 확인할 수 있다.
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 logTextLength<T>(text: T): T {
  console.log(text.length) //이건 오류! 왜냐면 어떤 타입이 들어올지 모른다고 생각하기 때문
  return text; 
}

logTextLength<string>("zz")
소스 안에서 text.length를 찍는다고 했을때 제네릭으로 받는다고 해도 저건 오류이다. 왜냐하면 제네릭으로 타입을 받으면 모든 타입이 가능하기 때문에 number가 넘어올지 boolean이 넘어올지 모르기 떄문이다. 이럴때 제네릭의 타입제한을 한다.

 


제네릭의 타입제한1 - T[ ] 

function logTextLength2<T>(text: T[]): T[] {
  console.log(text.length);
  text.forEach(function (text) {
    console.log(text)
  })
  return text; 
}

logTextLength2<string>(["zz","test"])
무조건 length가 있는 []로 잡고 string을 보내면 string[] 가 되도록 하여 length가 문제없도록 하였다.

 


제네릭의 타입제한2 - extends

extends를 통해서 LengthType이라는 interface로 정의한 것이다.

제네릭의 타입제한3 - extends keyof

extends keyof 는 선언해놓은 interface 의 key중에 하나에 맞춰서 쓰겠다 라는 뜻이다.

 

반응형