[TIL] typescript Object.entries 사용 할 때 타입 지정 방법

ahn__jh·2023년 5월 19일
3
post-thumbnail

블로그 작성 계기

나는 타입스크립트를 사용하면서 엔터티 혹은 DTO를 사용 할때 만 타입 추론만을 확인하고 정작 필요한 복잡한 구현 로직들의 타입은 개발하기 급급하며 까먹고 타입 지정을 놓치거나 까먹은 경우가 있었다.

타입스크립트를 사용 하지만 협업에 있어서 다른 개발자가 작성한 코드를 읽을 때 좋은 기능을 사용하지 않고 있어서 까먹지 않기 위한 노력 + 공유 하려고 블로그를 작성합니다.

type C = {
  id: number;
  email: string;
};

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

interface ITemplate2 {
  STEP_A: A[];
  STEP_B: A[];
  STEP_C: C[];
}

const test = (template: ITemplate2) => {
  Object.entries(template).map(([key, value]) => {
    return [key, value];
  });
};

위 코드 처럼 test함수의 파라미터로 template을 받는다 template은 Template2라는 타입을 가지고 있다.

그런데 Object.entries, values, keys 자바스크립트의 내장 함수를 사용 할 때 보면 타입이 깨져

아래 사진처럼 타입이 key는 string value엔 any가 추론된다.

이런 타입이 만약 깊이가 깊어져 타입이 3개 4개 까지 들어간다면 string, any로는 무엇이 나오는지 log를 찍어보거나 디버그 모드로 확인 해야 하고 눈과 생각으로만 읽기엔 어렵다.

ex) 여기서 말하는 타입이 깊다는 뜻은 타입안에 타입이 존재하고 그 하위 타입이 또 타입이 가졌을 때를 말씀 드립니다.

이제 위 코드의 타입을 지정 하기 위해 새로운 타입을 만들었다.

export type Entries<T> = {
  [K in keyof T]: [K, T[K]];
}[keyof T][];

아래 코드로 타입을 assertion 한다.

const test = (template: ITemplate2) => {
  (Object.entries(template) as Entries<ITemplate2>).map(([key, value]) => {
    return [key, value];
  });
};


의도한대로 Object.entries를 map을 돌렸을 때template의 key변수에 interface의 키인 STEP_A, B, C들이 IDE에서 타입을 추론 해주기 시작했다.
value 변수엔 A[] | C[]로 잘 나온다!

type C = {
  id: number;
  email: string;
};

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

interface ITemplate2 {
  STEP_A: A[];
  STEP_B: A[];
  STEP_C: C[];
}

이렇게 타입을 지정했을 때 장점이 한 type의 깊이가 깊고 데이터를 가공 해야 할때 무조건 loop를 돌릴 수 밖에 없는 상황이 생겼다고 가정 한다면,

앞서 말했 듯 코드가 길어지고 복잡해지는데 작성한 코드들의 값을 읽는 것 만으로는 알 수 없다.

???: 엥?.. 그래서 Entries type이 뭔지 모르겠어 설명해봐

Entries 타입을 설명 하겠습니다.

export type Entries<T> = {
  [K in keyof T]: [K, T[K]];
}[keyof T][];

type C = {
  id: number;
  email: string;
};

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

interface ITemplate2 {
  STEP_A: A[];
  STEP_B: A[];
  STEP_C: C[];
}

Entries는 오브젝트 유형 T를 매개변수로 사용하는 일반 유형입니다.

{ [K in keyof T]: [K, T[K]] }는 keyof T에서 얻은 키의 합집합에서 각 키 K를 반복하는 매핑된 유형이다.

Entries<Template2>이 들어가면

[K in keyof T] = Template2의 키인 STEP_A, STEP_B, STEP_C가 할당된다.

각 키 'K'에 대해 동일한 키 'K'로 속성을 정의하고 여기에 배열 유형 '[K, T[K]]'를 할당한다.

[K, T[K]]
  
K = STEP_A | STEP_B | STEP_C
  
T[K] = Template2의 value인 C[] | A[]가 할당 되는거죠

이 배열 유형은 'K'가 키이고 'T[K]'가 해당 값인 키-값 쌍을 나타낸다.

[keyof T][]는 매핑된 유형에 적용되는 인덱스 액세스 연산자입니다.

키-값 쌍의 배열을 제공하는 매핑된 유형에 정의된 모든 속성의 값을 추출합니다.

[keyof T][] = [[STEP_A, A[]], [STEP_B, A[]], [STEP_C, C[]]] 타입이 할당 된다.

따라서 Entries를 사용하면 TypeScript는 T의 키와 값을 유추하고 개체의 모든 키-값 쌍으로 구성된 배열 유형을 만듭니다.

2개의 댓글

comment-user-thumbnail
2023년 10월 11일

글 잘봤습니다!

d.ts파일 하나 만들어서 interface ObjectConstructorentries 메서드 반환형을 덮어씌우고
tsconfig파일에서 include해주면
as로 assertion을 해주지 않아도 key가 추론이 잘 되더라구요.
제가 생각할때 나름대로 꿀팁이라 댓글 달아봤습니다!

1개의 답글