웹개발 상식

[Generic] 제네릭 개념

lado 2021. 8. 8. 17:02

Generic

Generic 개념을 정리하는 목적

  • API 문서를 읽는 능력을 기르기 위해

 

api문서예시

 

Generic이란?

  • 정적 타입 언어에서, 재사용 가능한 컴포넌트를 생성하는 주요 도구
  • 제네릭은 어떤 클래스 혹은 함수에서 사용할 타입을 선언 시점이 아니라 생성 시점에 타입을 결정해서 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 프로그래밍 기법
  • 제네릭은 타입을 마치 함수의 파라미터처럼 사용한다

 

정적 타입 언어 vs 동적 타입 언어

  • Java나 C# 같은 정적 타입 언어의 경우, 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언하여야 한다. 그래서 기본적으로는 특정 타입을 위해 만들어진 클래스나 함수를 다른 타입을 위해 재사용할 수가 없다.
    자바예시
  • JavaScript는 동적 타입 언어. 변수를 정의할 때 타입을 선언하지 않는다. 값을 할당할 때, 할당하는 값의 타입에 의해 자동으로 반환값의 타입이 결정되기 때문이다. 그래서 실수로 의도와 다른 타입의 값을 할당했을 때, 런타임 에러가 나기 전에는 타입 에러를 잡을 수 없다는 문제가 있다.
  • let text = "hello";
    console.log(typeof text); // string
    console.log(text.charAt(0)); // h
    
    text = "5" - "3"; // 의도하지 않은 실수 ❗❗❗
    console.log(typeof text); // number
    console.log(text.charAt(0)); // TypeError: text.charAt is not a function
     
  • 타입 에러를 런타임이 아닌 컴파일 단계에서 잡을 수 있도록 동적 타입 언어의 문제점을 보완한 정적 타입 언어인 Typescript가 등장한다.
  • function identity(arg: number): number {
       return arg;
    }
    // identity 함수는 number 타입의 인자 값을 받고 number 타입의 반환 값을 반환하는 함수

 

Generic을 사용하는 이유

다양한 타입에서 작동하는 컴포넌트를 만들어 봅시다.

 

제네릭이 없다면

  • 일일이 타입에 대응하는 함수를 만들어줘야 한다.
function identityNum(arg: number): number {
   return arg;
}

function identityStr(arg: string): string {
   return arg;
}

function identityBool(arg: boolean): boolean {
   return arg;
}
  • 어떤 타입이든 받을 수 있는 any를 사용할 수도 있다. 하지만 함수의 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지 알 수 없다. any라는 타입은 타입 검사를 하지 않기 때문이다. (타입이 보장되지 않기 때문에 타입스크립트에서 any를 사용하는 것을 권장하지 않는다.)
function identityAny(arg: any): any {
   return arg;
}

let imAny = identityAny(123);
imAny = identityAny("나는 문자다");

image

 

제네릭을 사용하면

  • 함수를 호출하거나 인스턴스를 생성할 때 인자 값의 타입에 따라 해당 함수에서 사용할 수 있는 타입을 컴파일러가 자동으로 정해준다. 다른 타입의 값을 인자로 할당하려고 하면 타입 에러를 띄운다.
function identity<GENERIC>(arg: GENERIC): GENERIC {
   return arg;
}

let imNumber = identity(123);
imNumber = identity("나는 문자다");
image
image

 

Generic 사용 예시

function identity<T>(arg: T): T {
  return arg;
}

// 함수를 호출하는 2가지 방법

let howToCall1 = identity<string>("myString");
let howToCall2 = identity("myString"); // type argument inference
  • 함수명 옆에 제네릭을 사용하겠다는 의미로 <> 꺽쇠를 넣고 T와 같이 그 안에 타입으로 사용되는 식별자를 넣는다.
  • T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 '타입 파라미터(Type parameter)'라 한다. T는 Type의 약자로 반드시 T를 사용하여야 하는 것은 아니다.
  • 함수를 호출할 때 일반적으로 2번째 방법을 사용한다. 코드도 더 짧고 가독성이 좋기 때문이다. 하지만 코드가 복잡해져서 명시적으로 타입을 표기해주고 싶다면 1번째 방법을 사용하면 된다.

 

Generic 문법

  • 클래스 예시
function identity<T>(arg: T): T {
  return arg;
}

// 함수를 호출하는 2가지 방법

let howToCall1 = identity<string>("myString");
let howToCall2 = identity("myString"); // type argument inference

 

  • 함수에서 배열 인자를 사용하고 싶을 때
class GenericNumber<T> {
   zeroValue: T;
   add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
   return x + y;
};

console.log(myGenericNumber.zeroValue); // 0
console.log(myGenericNumber.add(1, 2)); // 3

 

  • 두 개 이상의 타입 변수
function toPair<T, U>(a: T, b: U): [T, U] {
   return [a, b];
}

console.log(toPair<string, number>("1", 1)); // [ '1', 1 ]
console.log(toPair<number, number>(1, 1)); // [ 1, 1 ]

 

참고 문서

'웹개발 상식' 카테고리의 다른 글

[프로그래밍 패러다임] 객체지향 프로그래밍  (0) 2021.09.18