본문 바로가기
typeScript

typeScript - 제네릭(Generic) 기법

by sinabeuro 2021. 7. 29.
728x90

제네릭 기법

타입스크립트에서는 특별한 타입을 지정해줄 수 있는 제네릭을 사용할 수 있습니다.

정의한 타입을 지정한다는 점에는 자바의 제네릭과 비슷한 점이 있습니다.

 

타입스크립트에서는 any라는 모든 타입을 받을 수 있는 타입이 존재합니다.

그럼 any를 쓰면 되지 제네릭을 쓰는 이유가 무엇일까요?

any를 이용하여 구현하면 저장하고 있는 자료의 타입이 모두 같지 않다는 문제점이 생깁니다.

예를 들면 any 타입으로 구현한 배열에 문자, 숫자 등 여러 타입이 함께 들어갈 수 있다는 것이죠.

따라서 위의 스택에서 자료를 추출하는 경우 런타임에서 항상 타입 검사를 해줘야 한다는 문제가 있습니다.

다른 방법으로 타입을 상속해서 몇 가지 타입만 허용하는 방법으로 구현을 할 수 있습니다.

하지만 코드랑이 길어지고 가독성이 떨어질 수 있기에 제네릭을 사용하는 것입니다.

 

 

제네릭 구현 문법 - 함수일 경우

function pipeOne(value: any): any {
  return value;
}

function pipeTwo<T>(value: T): T {
  return value;
}

let p1 = pipeOne(10);
let p2 = pipeTwo('10');
let p3 = pipeTwo(true);
console.log(p1);	// 10
console.log(p2);	// 10
console.log(p3);	// true

함수로 제네릭을 구현할 경우 함수명 뒤에 <T>를 붙여주고 파라미터, 리턴타입에 T를 기술하면 됩니다.

<T>를 선언하면 T를 타입 변수 취급하며, 함수 내에서 타입 변수로써 사용할 수 있습니다.

 

type User = {
  id: number;
  name: string;
}

const pipeObjectOne = <T>(obj: T): T => {
	return obj;
}

let po1 = pipeObjectOne({id: 1, name: '김', zipcode: 50213 });
let po2 = pipeObjectOne<User>({id: 1, name: '김', zipcode: 50213 });
console.log(po1);
console.log(po2);

 

 

제네릭 구현 문법 - 클래스일 경우

class Stack<T> {
  private data: T[] = [];

  constructor() {}

  push(item: T): void {
    this.data.push(item);
  }

  pop(): T {
    return this.data.pop();
  }
}

const numberStack = new Stack<number>();
const stringStack = new Stack<string>();
numberStack.push(1);
stringStack.push('a');

각 스택은 항상 생성할 때 선언한 타입만을 저장하고 리턴합니다.

이렇게 하면 컴파일러가 리턴하는 타입을 알 수 있게 되므로 에디터에서 자동 완성을 사용할 수 있게 되므로 생산성 향상에도 기여한다는 장점이 있습니다.

 

 

제네릭 구현 예시 - 두 개 이상의 타입 변수(함수)

function toPair<T, U>(a: T, b: U): [ T, U ] {
  return [ a, b ];
}

toPair<string, number>('1', 1); // [ '1', 1 ]

 

 

제네릭 구현 예시 - 두 개 이상의 타입 변수(클래스)

type Address {
	zipcode: number;
    address: string;
}

class State<S, Config={}> {
  private _state: S;
  config: Config;
  
  constructor(state: S, config: Config) {
    this._state = state;
    this.config = config;
  }
  
  getState(): S {
    return this._state;
  }
}

let s1 = new State<Address, { active: boolean }>({
  zipcode: 50213,
  address: '서울시',
}, {
  active: true
});


const s1Data = s1.getState();
// 타입 추론 가능
console.log(s1Data.zipcode, s1Data.address, s1.config.active);

 

 

제네릭 확장(심화)

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
	return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

console.log(getProperty(x, "a"));
console.log(getProperty(x, "m"));	// 에러가 발생함. 파라미터로 넘긴 객체에 없는 key임

객체를 파라미터를 넘기고 그 객체 안에 있는 특정 value 값을 가져오는 예제입니다.

extends 지시어로 상속을 하는데, keyof 지시어로 제네릭 타입 Type의 key값만 상속할 수 있습니다.

즉, Key 제네릭 타입은 Type 제네릭 타입의 key값으로 이루어진 타입이됩니다.

이러한 방법으로 객체 내에 key을 체크하여 value를 반환하는 기능을 구현할 수 있습니다.

 

 

interface KeyPair<T, U> {
    key: T;
    value: U;
}

let kv1: KeyPair<number, string> = { key: 1, value: 'kim'};
let kv2: KeyPair<number, number> = { key: 2, value: 12345};

인터페이스에 제네릭으로 타입을 지정함으로써, 생성할 객체의 유효성검사를 할 수 있습니다.

 

 

class Stack<T> {
  private data: T[] = [];

  constructor() {}

  push(item: T): void {
    this.data.push(item);
  }

  pop(): T {
    return this.data.pop();
  }
}

function getFirst<T extends Stack<U>, U>(container: T): U {
  const item = container.pop();
  container.push(item);
  return item;
}

getFirst<Stack<number>, number)(numberStack);
getFirst<number, number>(1); // Type 'number' does not satisfy the constraint 'Stack<number>'

타입 변수는 기존에 사용하고 있는 타입을 상속할 수도 있습니다.

이 점을 이용하면 입력 받을 변수의 타입을 제한할 수 있습니다.

 

 

 

 

https://hyunseob.github.io/2017/01/14/typescript-generic/

 

TypeScript: 제네릭(Generic)

이전 글 - TypeScript: 함수(Function) 제네릭은 Java 등의 정적 타입 언어를 사용하던 사람에게는 익숙한 단어일지도 모르겠다. 그러나 JavaScript를 사용해왔던 개발자에게는 그렇지 않다. 제네릭은 어떠

hyunseob.github.io

 

728x90

'typeScript' 카테고리의 다른 글

typeScript - @types  (0) 2021.08.01
typeScript - 타입가드  (0) 2021.08.01
typeScript - 튜플  (0) 2021.07.26
typeScript - typeScript 개괄3(기타)  (0) 2021.07.07
typeScript - typeScript 개괄2(객체 생성과 접근자 제한)  (0) 2021.07.07

댓글