TypeScript-제네릭이란

hannah·2023년 9월 26일
0

JavaScript

목록 보기
97/121

제네릭(Generics)

클래스 또는 함수에서 사용할 타입(Type)을, 그 클래스나 함수를 사용할 때 결정하는 프로그래밍 기법

  • 동적 타입 언어인 JavaScript와 달리 정적 타입 언어인 TypeScript에서는 제네릭을 지원한다.
  • 정적 type 언어는 클래스나 함수를 정의할 때 type을 선언해야 한다.
  • 코드를 작성할 때 식별자를 써서 아직 정해지지 않은 타입을 표시한다.

- 제네릭의 특징

제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다. 한번의 선언으로 다양한 타입에 재사용이 가능하다는 장점이 있다.

T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 타입 파라미터(Type parameter)라 한다. T는 Type의 약자로 반드시 T를 사용하여야 하는 것은 아니다. 이와 비슷하게 U, V, ...를 사용하기도 하고 필드 이름의 첫 글자를 사용하기도 한다.

또한 함수에도 제네릭을 사용할 수 있다. 제네릭을 사용하면 하나의 타입만이 아닌 다양한 타입의 매개변수와 리턴값을 아래 예제처럼 사용할 수 있다.

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}

reverse 함수는 인수의 타입에 의해 타입 매개변수가 결정된다. Reverse 함수는 다양한 타입의 요소로 구성된 배열을 인자로 전달받는다. 예를 들어 number 타입의 요소를 갖는 배열을 전달받으면 타입 매개변수는 number가 된다.

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}

const arg = [1, 2, 3, 4, 5];
// 인수에 의해 타입 매개변수가 결정된다.
const reversed = reverse(arg);
console.log(reversed); // [ 5, 4, 3, 2, 1 ]

만약 {name: string} 타입의 요소를 갖는 배열을 전달받으면 타입 매개변수는 {name: string}가 된다.

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}

const arg = [{ name: 'Lee' }, { name: 'Kim' }];
// 인수에 의해 타입 매개변수가 결정된다.
const reversed = reverse(arg);
console.log(reversed); // [ { name: 'Kim' }, { name: 'Lee' } ]

- 제네릭을 사용하는 이유

TypeScript로 구현한 Model 클래스는 일반적으로 특정 데이터 타입을 규정하지 않고, 어떤 타입이든 아이템으로 추가하거나, 추출할 수 있다.

class Model {
 private _data: any[] = [];

 constructor(data: any[]) {
   this._data = data;
 }

 get data(): any {
   return this._data;
 }

 add(item: any): void {
   this._data.push(item);
 }

 remove(index: number): void {
   this._data.splice(index, 1);
 }

 item(index: number): any {
   return this._data[index];
 }

 clear(): void {
   this._data = [];
 }
}

하지만 특정 데이터 타입을 규정한 Model 클래스를 사용하고자 할 경우 클래스 상속을 이용한 새로운 클래스를 생성해 문제를 해결할 수 있다. 예를 들어 객체 타입만 데이터로 추가할 수 있도록 하기 위한 ObjectModel 클래스를 정의한다면 아래의 코드와 같이 구성한다.

class ObjectModel extends Model {
 constructor(data: object[] = []) {
   super(data);
 }

 add(item: object): void {
   super.add(item);
 }
}

하지만 클래스 상속을 사용하면 별도의 자료 타입을 받고자 하는 클래스를 추가해야 하고 중복되는 코드를 양산하기에 불편하다. 바로 이런 경우에 유용하게 사용할 수 있는 것이 제네릭이다.

즉,

1. 재사용성이 높은 함수와 클래스를 생성할 수 있다.

- 여러 타입에서 동작이 가능한다.( 한번의 선언으로 다양한 타입에 재사용할 수 있다.
- 코드의 가독성이 향상된다.

2. 오류를 쉽게 포착할 수 있다.

- _any 타입을 사용하면_ 컴파일 시 타입을 체크하지 않는다.
타입을 체크하지 않아 관련 메소드의 힌트를 사용할 수 없다.
컴파일 시에 컴파일러가 오류를 찾지 못한다.

- **제네릭도 any처럼 미리 타입을 지정하지는 않지만 타입을 체크해 컴파일러가 오류를 찾을 수 있다.**

0개의 댓글