본문 바로가기

TIL

TS. Generics 그리고 코드 중복에 관해

오늘은 간단하게 제네릭을 정리하려고 한다. 제너릭을 사용하는 이유는 코드의 중복을 없애는 게 가장 큰 목적이 아닐까 한다.  기본적으로 같은 기능을 하는 함수들이 다른 타입의 인자를 받게 될때, 제너릭을 사용해서 같은 함수로 처리할 수 있게 해 준다. 간단한 예제를 보자.  같은 기능을 하는 함수를 불필요하게 여러 개 만들 필요가 없다.

function getNumber(value: number) {
  return value;
}

function getArray(value: string[]) {
  return value;
}

// 제네릭 기본 문법 - 함수
function getValue<T>(value: T): T {
  return value;
}
getValue('hi').toLocaleUpperCase();
getValue(100).toLocaleString();

아 근데 이런 코드 쪽의 중복은 쉽게 파악이 가능한데 타입 쪽 중복을 찾아서 제거하는 게 덜 익숙해서 중복이 많다고 한다. 그래서 간단한 제네릭 사용법은 다른 곳에서 보면 잘 나와있으니까 코드 중복에 관한 내용으로 정리를 하겠다.

먼저 간단한 방식으로 타입의 중복을 줄이는 방식을 보자 다음과 같다. 저번에 정리한 인터페이스에서 확장하는 개념이 하나의 방법이다.

interface Person{
    firstName:string;
    lastName:string;
  }
  
  
interface PersonWithBirthDate extends Person{
	birth:Date;
}

type PersonWithBirthDate = Person & {birth:Date}

 

이번엔 다른 측면을 생각해 보자. 전체 애플리케이션의 상태를 표현하는 State 타입과 부분을 표시하는 TopNavState가 있는 경우를 보자

interface State{
    userId:string;
    pageTitle:string;
    recentFile: string[];
    pageContents: string;
}

interface TopNavState{
    userId:string;
    pageTitle: string;
    recentFiles: string[];
}

TopNavState를 확장해서 State 구성하는 거보다 State의 부분 집합으로 TopNavState를 정의하는 것이 바람직해 보인다. State를 인덱 싱하여 속성의 타입에서 중복을 제거할 수 있다.

type TopNavState={
    userId:State['userId']
    pageTitle:State['pageTitle']
    recentFiles:State['recentFiles']
    }

여전히 중복되는 코드가 존재한다. 그래서 아래와 같이 바꿀 수 있다.

type TopNavState={
	[k in 'userId' | 'pageTitle' | 'recentFiles'] : State[k]
 }

이 패턴은 표준 라이브러리에서도 일반적으로 찾을 수 있고 이러한 타입을 Pick이라고 한다. Pick 은 제네릭 타입이다. Pick을 사용하는 것은 함수를 호출하는 것과 마찬가지라고 한다. 

type Pick<T,K> = { [k in K] : T[k]};
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;

이 외에도 추가적으로 Partial이나 typeof, ReturnType 같은 유용한 타입들이 있다. 나중에 따로 찾아보자. 

마지막으로 제네릭에서 타입 제한하는 방법을 알아보며 끝내겠다.

다음 예시를 보면 couple2는 Name을 확장하지 않기 때문에 오류가 발생한다. 여기서 pick을 사용해 따로 타입을 만드는데 위에서 보여준 Pick의 K는 범위가 넓다 그래서 실제로 라이브러리에 구현되어 있는 Pick은 다음과 같다. 타입의 키값으로 제한을 둬서 구현하게 하였다.

따라서 다음과 같이 타입을 선언해주면 에러를 발생시킨다.

정리하자면,

타입에서도 최대한 반복되는 코드를 지워야 하고, 타입에 이름을 붙여서 반복을 피해야 한다. extends를 사용해서 인터페이스 반복을 피해야 한다. 또한 위에서 언급한 Pick, Partial, ReturnType, keyof , typeof 같은 타입에 익숙해지는 것이 좋겠다.

 

참고 및 출처: <이펙티브 타입스크립트> (댄 밴더캄 지음, 장원호 옮김, 인사이트 ,2021) , <타입스크립트 입문> (인프런 강의 , 장기효)