[TypeScript] 제네릭

Seju·2023년 10월 10일
1

TypeScript

목록 보기
1/1

💻 제네릭


😵 제네릭이란?


  • 기존 c#이나 Java 등에서 사용되던 개념
  • 기존 자바스크립트엔 존재하지 ❌
  • 제네릭(generic)이란 타입을 마치 함수의 파라미터처럼 유동적으로 사용하는것을 의미한다
    • 타입의 변수화
  • 제네릭은 일반적인 생성 시점에 타입을 명시하는 것이아닌, 생성 시점에 타입을 명시해 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다
    • 즉, 재사용 가능한 컴포넌트를 만들 수 있게 해주는게 핵심적인 기능이다

🤹‍♀️ 제네릭이 필요한 경우?


아래 코드에서 매개변수 선언시

  • 아래 arr2 변수는 string으로 이루어진 배열이므로 getSize함수 파라미터에서 유니온타입으로 string[]를 선언해줘야함
    • 만약 arr3,arr4,arr5.. 이런 배열들이 하나같이 타입이 다르다면?
    • 일일히 지정해줘야하는 불편함이 생긴다
    • 해당 값들을 유동적으로 개발자가 받을 수 있도록 지정하고 싶을때
const getSize = (arr:number[]) : number => {
    return arr.length;
}

const arr1 = [1,2,3];
getSize(arr1); //3 

const arr2 = ['A','B','C']
getSize(arr2); //error

🐒 제네릭 기본 문법


제네릭 함수 선언

  • 여기서 <T>는 타입 변수로써 함수 내부에서 임의의 타입으로 동작
    • 해당 타입변수는 어떠한 타입도 될 수 있는 유연한 타입이다
  • (arg: T):T 함수의 파라미터 부분과 리턴값도 타입변수로 받게 되는데,
  • 실제 함수를 호출할때 해당 호출부에서 <T>타입이 어떤 타입을 받아야하는지 작성해야한다
  • 요약하자면, "제네릭함수인 "identitty는 어떠한 데이터 타입으로 들어오는 간에 같은 데이터타입으로 리턴해주겠다는" 뜻이다
function identity<T>(arg: T): T {
    return arg;
}

제네릭 함수의 호출

  • 아래 코드에서 output 변수는 string이란 타입을 명시적으로 지정해주었으며, 해당 함수의 리턴값도 역시 string타입이 될것이다.
let output = identity<string>("myString");  
let numberOutput = identity<number>(10);

🐻 제네릭 타입변수 작업


강제성

  • 제네릭을 사용하게 되면 identity와 같은 함수를 만들때, 타입스크립트 컴파일러가 함수 본문에 입력된 매개변수를 올바르게 사용하도록 강제한다.
    • 즉, 실제로 이러한 매개변수를 모든 유형이 될 수 있는것처럼 취급한다.
function identity<Type>(arg: Type): Type {
  return arg;
}

이렇게 쓰고 싶은 유혹을 받을 수도..

  • 위에서 타입변수는 모든 타입을 대신할 수 있다고 정의했었다.
    • 따라서 해당 함수를 사용하는 사용자가 length 프로퍼티가 없는 타입을 대신 전달할 수도 있음을 타입스크립트가 알려주고 있다
function loggingIdentity<Type>(arg: Type): Type {

	/* Property 'length' does not exist on type 'Type'*/
  console.log(arg.length);

  return arg;
}

올바른 사용법은?

  • loggingIdentity함수의 유형은 매개변수로 배열인 Type을 받고 최종적으로 Type 배열을 반환하도록 작성해야한다
  • 제네릭 타입 변수 Type을 전체 타입이 아닌 작업 중인 타입의 일부로 사용할 수 있으므로 유연성이 향상된다
function loggingIdentity<Type>(arg: Type[]): Type[] {
  console.log(arg.length);
  return arg;
}


/*배열의 타입선언은 Array로도 가능하니 이방법도 OK*/
function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
  console.log(arg.length); // Array has a .length, so no more error
  return arg;
}


🦔 제네릭 제약조건


loggingIdentity 함수

  • 해당 함수에서 <T> 변수에 length라는 속성이 존재하지 않으므로 타입에러가 나타나고 있다
function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length);

// Error Property 'length' does not exist on type 'Type'.
  return arg;
}

💡 해당 함수가 모든 조건에 동작하는 대신 .length 메서드가 존재하는 모든 유형에서만 작동하도록 제한하고 싶다면?

순서

  • 먼저 어떠한 타입이 될 수 있는지에 대한 요구사항을 나열해야한다
  • 제약 조건을 설명할 수 있는 인터페이스를 만드는것도 가독성에 도움이 된다
  • extends라는 키워드를 통해 타입변수를 확장시킨다
interface Lengthwise {
  length: number;
}
 
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {

// Now we know it has a .length property, so no more error
  console.log(arg.length); 
  return arg;
}

🐝 제네릭 함수 인터페이스


다음은 제네릭을 사용한 함수 인터페이스의 예시이다

  • 아래 코드에서 GenericIdentityFn 인터페이스는 <T>는 타입변수이며, arg 파라미터와 반환값 또한 같은 타입이 들어오면 같은 타입으로 나가도록 한다
  • 그리고 identity라는 실제 함수가 있으며, 이 함수는 어떠한 타입의 T의 값을 받아 그대로 반환하는 로직이 있다
  • 마지막으로 myIdentity 변수에 identity 함수를 할당하는데 정의한 인터페이스를 명시적으로 선언하였다.
    • 이렇게 함으로써 myIdentity 변수가 GenericIdentityFn 인터페이스를 따르는 어떤 함수든 가질 수 있다고 명시하게 된다
interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;

특정타입을 지정해줘야하는 인터페이스 구현

  • 위의 예시 코드와의 차이점은 바로 제네릭 파라미터 <T>를 인터페이스 이름 바로 뒷부분으로 옮겼다
    • 그럼으로써 해당 파라미터가 전체 인터페이스에 적용되고 마지막으로 myIdentity 변수를 선언하고 해당 인터페이스의 타입을 적용할때 <number> 타입만 처리하는 GenericIndentityFN 함수라고 명시적으로 선언하였다.
interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

위 두 코드의 차이점은?

  • 첫번째 방식
    • 같은 함수이지만 다양한 타입에서 동작해야하는 경우
  • 두번째 방식
    • 특정한 타입에 대해서만 동작해야하는 경우

🦢 제네릭 클래스

  • 제네릭 인터페이스와 모양이 비슷하다
  • 클래스 이름 뒤에 <>로 묶인 제네릭 타입 매개변수가 있다
class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}
 
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

GenericNumber 클래스를 문자 그대로 사용하면?

  • 숫자 유형만 사용하도록 제약 ❌
  • 대신 문자열이나 훨씬 더 복잡한 객체를 사용 가능해진다
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
  return x + y;
};
 
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
profile
Talk is cheap. Show me the code.

0개의 댓글