본문 바로가기
📌 Front End/└ TypeScript

[TypeScript] 제네릭(Generic)

by 쫄리_ 2024. 8. 4.
728x90
반응형

다형성(Polymorphism)이란?

poly는 '많다, 많음' 라는 뜻이고

morp-는 '구조, 형태' 라는 뜻을 가지고 있습니다.
따라서 Polymorphism, 다형성이란 여러가지 다양한 구조, 모양, 형태 라는 뜻을 가지고 있습니다.
타입스크립트에서 이런한 다형성을 가능하게 해주는 것은 

바로 제네릭(Generics) 타입입니다.


제네릭(Generic)

제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미

제네릭은 타입을 선언 시점이 아닌 생성 시점에 결정하는 프로그래밍 기법입니다.

 

제네릭을 선언할 때 관용적으로 사용되는 식별자로 T 또는 V를 많이 사용을 하며,
이는 타입 파라미터(Type parameter)라 합니다.

그리고 이것은 반드시 T 또는 V로 적어야 하는거는 아닙니다.


이를 통해 함수, 클래스, 인터페이스 등을 다양한 타입에 대해 재 사용할 수 있습니다.


📌 제네릭 문법

  1. <> 안에 식별자를 써서 아직 정해지지 않은 타입을 표시할 수 있다.
  2. <> 안에 적은 식별자는 블록 내 어디서든 사용 가능하다.
  3. 이후 사용할 때는 <> 안에 정확한 타입을 적어줘야 한다.

 

📌 제네릭 컨벤션

제네릭 타입 변수를 여러개 쓸 경우 T, U, V, W, X, Y, Z 알파벳 순서로 사용한다.

 

📝 제네릭 선언

// ▶제네릭 함수
function 메서드명<타입변수명>(매개변수명: 타입변수명): 리턴타입변수명 {
	/* 매개변수명 사용 */
}
// ▶제네릭 클래스
class 클래스명<타입변수명> { /* 타입변수명 사용 */ }
// ▶제네릭 인터페이스
interface 인터페이스명<타입변수명> { /* 타입변수명 사용 */ }

 


제네릭 종류

타입스크립트에서 제네릭은 함수, 인터페이스, 클래스 등에 사용할 수 있습니다.

  • 제네릭 함수
  • 제네릭 클래스
  • 제네릭 인터페이스

제네릭 함수

<>를 사용해서 제네릭을 적용할 수 있습니다.
함수를 호출할 때에 <>를 사용해서 타입을 지정해줄 수 있다.

함수는 호출 시 타입 매개변수를 생략하면 타입을 추론하여 실행됩니다.

함수의 인자에 따라 return 값이 동일하게 만들기 위해서는 제네릭 타입변수를 활용할 수 있다.

function number<T>(a : T[]) {
  return a[0]
}
console.log(number(['1a', '2a', 3, 4]));   // 1a

 

➕ 화살표함수 사용

const arr = <T>(a: T[]) => a[0];

console.log(arr(['ㄱ', 'ㄴ', 'ㄷ', 'ㄹ']));	// type> string, output> ㄱ 
console.log(arr([1, 2, 3, 4];			// type> number, output> 1

 

* 리엑트에서 제넥릭을 화살표함수로 구현하면 에러가 생긴다.

   tsx 파일에서는 <T>를 태그로 인식하기 때문입니다.
   이를 해결하려면 extends를 사용하여 제네릭임을 알려야 합니다.

// Error
const func = <T>(arg: T): T => {
  return arg;
}

// Ok
const func = <T extends {}>(arg: T): T => {
  return arg;
};

// 함수 선언식
function output1<Type>(input: Type): Type {
  return input;
}

// 함수 표현식
const output2: <Type>(input: Type) => Type = (input) => {
  return input;
}

// 객체 리터럴 타입의 함수 호출 시그니처 - 인터페이스로 뺄 수 있다
const output3: { <Type>(input: Type): Type } = (input) => {
  return input;
}

console.log(output1<number>(1));	// 1
console.log(output2<string>('1'));	// "1"
console.log(output3<boolean>(true));	// true

 

사용예시

function age<Type>(value: Type>: Type {
	return value;
}

// 타입 파라미터를 여러 개 사용할 수도 있습니다.
function age<T1, T2>(value1: T1, value2: T2): T2 {
	console.log(value1);
	return value2;
}

 


제네릭 클래스

클래스명 뒤에 꺽쇠와 제네릭 타입변수를 작성하고, 타입 변수를 사용하면 된다.

- 제네릭 인터페이스와 형태가 유사하다. Type 식별자는 블록 내 어디서든 사용 가능하다. 
- static 멤버는 클래스의 타입 매개변수를 쓸 수 없다. 
- 생성자 함수를 호출할 때 타입 명시를 해준다. 

class Queue<Type> {
  data: Array<Type> = [];
  
  push(item: Type) {
    this.data.push(item);
  }
  
  pop(): Type | undefined {
    return this.data.shift();
  }
}

const numberQueue = new Queue<number>(); // 타입 명시
const stringQueue = new Queue<string>(); // 타입 명시

 

 

사용예시

일반 클래스를 만들어봤는데요.
해당을 제네릭 클래스로 바꿔보겠습니다.

class Person {
	name: string;
	age: number;
	constructor(name: string, age: number) {
		this.name = name;
		this.age = age;
	}
}

const p = new Person('Rabbit', 10);
p.name;	// 'Rabbit'
p.age;	// 10

 

일반 클래스 → 제네릭 클래스 변경

// name에 대한 타입을 N, age에 대한 타입을 A로 표현했습니다.
class Person<N, A> {
	name: N;
	age: A;
	constructor(name: N, age: A) {
		this.name = name;
		this.age = age;
	}
}

const p = new Person<string, number>('Rabbit', 10); // 타입 명시
p.name;	// 'Rabbit'
p.age;	// 10

 

 

➕ 생성자 함수에 제네릭 적용 방법

function create<Type>(c: { new (): Type }): Type {
  return new c();
}

 


제네릭 인터페이스

인터페이스 타입을 받아 사용할 때 

뒤에 꺽쇠와 제네릭 타입변수를 작성하고, 타입 변수를 사용하면 된다.

 

제네릭을 인터페이스에 사용될 수 있습니다.

interface Foo<T, U> {
  x: T;
  y: U;
}

 

제네릭을 사용한 함수 인터페이스는 2가지 방식으로 표현 가능합니다. 

 

interface Bar<T> {
  (text: T): T;
}

interface Bar {
  <T>(text: T): T;
}

 


사용예시

- GenericOutput1은 제네릭 인터페이스이므로 인터페이스 적용 시에 타입을 명시해줬다.

- GenericOutput2는 구성요소가 제네릭이므로 함수 호출 시에 타입을 명시해줬다. 

// 1. 제네릭 인터페이스
interface GenericOutput1<Type> {
  (input: Type): Type;
}

// 2. 인터페이스 내부에서 제네릭 사용
interface GenericOutput2 {
  <Type>(input: Type): Type;
}

function output<Type>(input: Type): Type {
  return input;
}

const myOutput1: GenericOutput1<number> = output;
const myOutput2: GenericOutput2 = output;

console.log(myOutput1(1));		// 1
console.log(myOutput2<string>('1'));	// "1"

 

 

사용예시

// 예시1
interface Age<T> {
	value: T;
}

// 예시2
interface AgeFunc {
	<T>(value: T) => T;
}

// 함수에 인터페이스를 적용
const age: AgeFunc = (value) => value;

age(11); // 11
age('11살') // '11살'

 


728x90
반응형