3 minute read

index signature와 Record type

리팩토링을 하던 도중 아래의 사진처럼 ~형식의 매개 변수가 포함된 인덱스 시그니처를 찾을 수 없습니다라는 에러가 떴었다. 즉, 인덱스 형식의 사용이 불가능 하다는 소리였다

image

오류 원인 🤔

// HomeworkBox.tsx
export const HOMEWORK_DATA = {
  WEB: {
    title: "자기소개 페이지를 첨부해주세요 (웹파트)",
    taskHelperText: "※ 학번.zip 파일 형식으로 첨부해주세요.",
  },
  SERVER: {
    title: "지원 과제 Repo 링크를 남겨주세요 (서버 파트)",
    taskHelperText: "※ 지원 기간이 지난 후의 commit 기록은 반영되지 않습니다.",
    LinkHelperText:
      "※ 다른 주소가 아닌 GitHub repository 링크 혹은 구글 드라이브 링크(액세스 권한-링크가 있는 모든 사용자)를 첨부해주세요.",
  },
};

HomeworkBox.tsx에서 HOMEWORK_DATAkey가 WEB, SERVER에 따라 title, taskHelperText 그리고 SERVER의 경우에는 추가로 LinkHelperText를 갖고있었다. 때문에 WEB: { title: string; taskHelperText: string; }과 같은 type을 지정해주하면 되지 않을까?라는 생각으로 처음에 코드를 작성했었다. 그렇다면 여기서 말하는 인덱스 시그니처(index signature)란 무엇일까?

index signature

  • 인덱스 시그니처는 {[key: T] : U } 형식으로
    • 객체가 여러개의 key를 가질 수 있으며 key와 매핑되는 value를 가지는 경우 사용한다
    • 특정 value에 점근하고 싶을 때, 해당 value의 key를 obj에 문자열로 인덱싱해 참조하는 방법
      • 객체의 특정 value에 접근하고 싶을 때
      • 객체의 속성들(key, value)의 모든 이름이나 type을 명확히 알지 못할 때, 속성의 type만 우선 지정해주어 객체의 정보들에 접근하기 위해 사용하는데 이때 반드시 key의 type은 string 혹은 number여야만 한다. 때문에 내 코드에서 오류가 발생했었다 🥲
      • 속성의 type을 알고있는 상황이라면, 정확한 type을 정의하여 실수를 방지할 수 있다는 장점이 있다
  • 인덱스 시그니처(index signature)는 대괄호로 객체를 접근하는 방법으로 사용 예시는 아래와 같다!

    // index signature
    type menuInfo = {
      [name: string]: number;
    };
    
    let food: menuInfo = {
      짜글이: 12000,
      볶음밥: 13000,
      카레: 14000,
    };
    
  • 인덱스 시그니처의 단점으로 내가 작성했던 코드처럼 문자열 리터럴을 key로 사용하는 경우 오류가 발생한다는 것이다. 그러면 이러한 문제는 어떻게 해결할 수 있을까? 🤔

Record Type

Record Type은 key로 문자열 리터럴을 허용한다!

이전의 index signature 코드를 Record Type을 사용하면 아래와 같다

// Record Type
type menuInfo = Record<string, number>;

let food: menuInfo = {
  짜글이: 12000,
  볶음밥: 13000,
  카레: 14000,
};

추가로 문자열 리터럴을 사용하여 아래와 같이도 적용할 수 있다!

type foods = "짜글이" | "볶음밥" | "카레";

type menuInfo = Record<foods, number>;

let human: menuInfo = {
  짜글이: 12000,
  볶음밥: 13000,
  카레: 14000,
};
  • Record<key, value> 와 같이 작성하며 키가 key 타입, value는 value타입인 객체 타입을 생성한다
  • 결론적으로 인덱스 시그니처와 유사한 기능을 수행하지만 차이점은 key로 문자열 리터럴을 사용할 수 있다는 것이다!

    // 아래와같은 인덱스 시그니처는 key에 문자열 리터럴 사용을 못함
    // 틀린 예시
    type Zelkovaria = {
      [name: "느티나무" | "hyein"]: number;
    };
    // Record를 사용한 올바른 예시
    type Names = "느티나무" | "hyein";
    type Zelkovaria = Record<Names, number>;
    
    let scores: Zelkovaria = {
      느티나무: 100,
      hyein: 200,
    };
    

오류 해결 💡

마찬가지로 오류 해결을 위해 IPartKey라는 type을 사용하여 IHOMEWORK_DATA의 key type을 지정하고, 이를 HOMEWORK_DATA에 적용해줌으로써 오류를 해결할 수 있었다!

export type IPartKey = "WEB" | "SERVER";
export type IHOMEWORK_DATA = Record<
  IPartKey,
  { title: string; taskHelperText: string; LinkHelperText?: string }
>;

export const HOMEWORK_DATA: IHOMEWORK_DATA = {
  WEB: {
    title: "자기소개 페이지를 첨부해주세요 (웹파트)",
    taskHelperText: "※ 학번.zip 파일 형식으로 첨부해주세요.",
  },
  SERVER: {
    title: "지원 과제 Repo 링크를 남겨주세요 (서버 파트)",
    taskHelperText: "※ 지원 기간이 지난 후의 commit 기록은 반영되지 않습니다.",
    LinkHelperText:
      "※ 다른 주소가 아닌 GitHub repository 링크 혹은 구글 드라이브 링크(액세스 권한-링크가 있는 모든 사용자)를 첨부해주세요.",
  },
};

그렇다면 이유는? type narrowing

관련 개념상 index signature는 특정 키의 타입 자체를 string 혹은 number로 상대적으로 넓은 범위를 지니고 있다. 즉, 상위개념인 string이라는 그 자체의 타입은 좁은 범위로 지정해둔 값의 하위개념으로 들어갈 수 없기 때문이라는 것을 알 수 있었다.

정리하자면, index signature와 달리 Record type은 키가 특정 값으로 제한 될 수 있기 때문에 타입을 좁힐 수 있다

이외의 ReactNode 할당 불가

ReactNode 할당 불가이슈 해결은 생각보다 더 간단했다,,ㅎ 현재 오류가 뜨는 부분인 errors[’link’]?.message는 string ~ undefined 타입을 반환해서 생긴 오류이다. 이를 해결하기 위해 해당 부분을 <></>로 감싸면 표현식이 ReactNode의 일부로 간주되어 오류없이 처리할 수 있다!

image

{errors['link']?.message}

<>{errors['link']?.message}</>

참고 자료

https://reactnative.dev/docs/react-node
https://soopdop.github.io/2020/12/01/index-signatures-in-typescript/
https://cheeseb.github.io/typescript/typescript-record/
https://velog.io/@ddowoo/Typescript-유틸리티-타입-Record-Type이란