본문 바로가기
programming language/TypeScript

제네릭(Generics) 재 정리!!

by cariño 2022. 12. 28.
728x90
반응형

제네릭은 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징 중 하나이다. 

한가지 타입보다 여러가지 타입에서 동작하는 컴포넌트를 생성하는데 사용된다. 

 

 

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

logText<number>(10);
logText<string>("ttt");
logText<boolean>(true);
제네릭은 logText의 함수를 호출할 때 파라미터의 타입을 넘길 수 있다. 
호출할 때 파라미터의 타입을 같이 지정하면서 넘기는 것이다.
 

 

[기존 타입 정의 방식과 제네릭의 차이점 알아보기]

1. 함수 중복 선언의 단점

- 타입을 다르게 받기위해 중복되는 함수 생성을 막을 수 있다. 

function logText(text:string) {
    console.log(text);
    // text.split("").reverse().join("");
    return text;
}
function logNumber(num:number) {
    console.log(num);
    return num;
}

 

 

2. 유니온 타입 방식의 문제점

정확한 타입 추론이 불가능하기에 타입별 api 기능을 제대로 사용할 수 없다.

- string과 number의 공통으로 받는 api속성에 대해서만 자동완성을 받는다. 

 

- 문자로 받은 변수 a는 타입이 string | number로 표기가 된다. 

- 문자열 메소드를 사용할 시에 에러가 표시가 된다. 

 

[제네릭을 사용하면 위 사항을 보완할 수 있다.]

실제로 함수를 정의할 때 타입을 비워놓음으로 호출하는 시점에 타입이 어떤것이 들어갈 것인지를 정의할 수 있다.

타입을 추론을 해서 최종 반환값까지 붙일 수 있다. 

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

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

제네릭을 사용하겠다는 선언과, 인자값과 리턴값 모두 타입이 제네릭이라고 지정해준다. 

변수 str은 string 타입으로 선언됐고 string의 메소드가 잘 사용이 되어진다. 

변수 login은 boolean타입으로 logText 함수가 재사용이 가능하다. 

 


[인터페이스에 제네릭을 선언하는 방법]

interface Dropdown {
    value: string
    selected: boolean
}

const obj: Dropdown = {
    value: 10,
    selected: false
}

기존에 인터페이스를 갖다 쓸 경우 지정해 둔 type에 맞게 사용해야 한다. 

 

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

const obj: Dropdown<number> = {//value의 값이 number
    value: 10,
    selected: false,
};

const obj: Dropdown<string> = {//value의 값이 string
    value: "abc",
    selected: false,
};

타입을 선언하는 시점에 타입을 추가적으로 넘김으로 Dropdown이라는 interface의 타입을 바꿀 수 있다. 

 


 

불필요한 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;
}

 


 정의된 타입으로 타입 제한하는 방법

 

function logText<T>(text: T): T {
  console.log(text.length); // Error: T doesn't have .length
  return text;
}

이러한 로직이 있을 때, 위 코드를 변환하려고 하면 컴파일러에서 에러를 발생시킨다. 

이유는 text.length에 대한 단서가 어디에도 없기 때문이다. 

함수의 인자와 반환 값에 대한 타입을 정의하지 않았지만, 입력 값으로 어떤 타입이 들어왔고 반환 값으로 어떤 타입이 나가는지에 대해서 추측할 수 있다. 

 

이러한 경우에는 제네릭에 타입을 제한해 줄 수 있다. 

function logText<T>(text: T[]): T[] {
  console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용.
  return text;
}

logText<string>(['hi', 'abc'])

 

제네릭 타입 변수에 대한 힌트를 줄 수 있는 방법이 있다. 

타입에 대한 강제는 아니지만 length에 대해 동작하는 인자는 넘겨 받을 수 있게 된다. 

interface LengthType {
    length: number;
}
function logTextLength<T extends LengthType>(text: T): T {
    text.length;
    return text;
}
logText(10); 
// Error, 숫자 타입에는 `length`가 존재하지 않으므로 오류 발생
logText({ length: 0, value: 'hi' });
// `text.length` 코드는 객체의 속성 접근과 같이 동작하므로 오류 없음
// length라는 속성이 들어가기만 하면 받아들일 수 있다는 의미

 

keyof로 타입 제한하기

두 객체를 비교할 때도 제네릭 제약 조건을 사용할 수 있다.

<O extends keyof T>

첫번째 인자로 받는 객체에 없는 속성들은 접근할 수 없다.

 

// keyof
interface IshoppingItem {
    name: string;
    size: number;
    stock: number;
}

// 인자값에 IshoppingItem의 속성중 하나만 받겠다고 타입에 제한을 줄 수가 있다.
function getShoppingItemOption<T extends keyof IshoppingItem>(item: T):T {
    return item;
}

getShoppingItemOption("name")
getShoppingItemOption("size")
getShoppingItemOption("stock")

 

728x90

댓글