[TS] 인덱스 시그니처에 대해 정리

이주영·2024년 3월 9일
0

typescript

목록 보기
3/4
post-thumbnail

카탈로그🔖 : 타입 시스템 > 동적 데이터에 인덱스 시그니처 사용하기

서론

매번 타입 스크립트를 사용하면서 객체의 키값 활용하여 값을 가지고 올 때 문제가 발생 해왔고 이를 해결하기 위해 인덱스 시그니처를 활용하여 객체 프로퍼티의 값을 가지고 왔는데 이 부분에 대해 깊이 알고 싶어 정리하고자 한다. 우선 타입 스크립트에서 인덱스 시그니처가 무엇이고 어떻게 사용하는 것인지 살펴보고 더 나은 타입을 적용할 수 있는지 고민해보려고 한다.

본론

인덱스 시그니처란?

인덱스 시그니처는 { [Key: T] : U } 형식으로 객체가 여러 Key를 가질 수 있으며, Key와 매핑되는 Value를 가지는 경우 사용한다.

const user = {
	name : "juyoung",
	location : 'seoul'
}

자바스크립트 객체는 문자열 키를 타입의 값에 관계없이 매핑한다.

type User = {[property : string] : string}
const user : User = {
	name : 'juyoung',
	location : 'seoul'
}  // 정상!! 

위의 타입에서 [property: string] : string이 인덱스 시그니처이다.

인덱스 시그니처를 3가지로 알아보자

1. 키의 이름 : 키의 위치만 표시하는 용도라고 한다. 즉 타입 체커에서는 사용하지 않는다. [property ...
2. 키의 타입 : string이나 number, symbol 그리고 Template Literal 타입이어야한다. 하지만 보통 string이라고 한다. [... : string]
3. 값의 타입 : 어떤 것이든 가능하다. : {[...] : string}

주의할 것

  1. 모든 키를 허용하는 문제, user라는 객체의 키를 타입 체크해주지 않는 문제가 발생한다.
  2. 키가 없어도 에러를 알려주지 않는 문제 : {} 도 유효한 User 타입으로 읽힌다.
type User = {[property : string] : string}
const user : User = {} // 정상 


type User = {
	name : string
	location : string
}

const user : User = {} // Type '{}' is missing the following properties from type 'User': name, location
  1. 각 키마다 다른 타입을 가질 수 없다는 것.
  2. 자동 완성을 지원해주지 못한다. 왜냐하면 정확한 키에 대한 타입이 없기 때문이다.

결론적으로 더 나은 방법이 있다는 것이 이 책에서 말하는 핵심

더 나은 타입을 지정하는 방법

1. Record를 사용한다.
Record는 키 타입에 유연성을 제공해 주는 제네릭 타입이다. 무엇보다 string의 부분 집합을 사용할 수 있다.

2. 매핑된 타입을 사용하는 방법이다. 매핑된 타입은 키마다 별도의 타입을 사용하게 해 준다.

type Vec3D = {[k in 'x' | 'y' | 'z'] : number}
// type Vec3D = {
// 	x : number;
// 	y : number;
// 	z : number;
// }

type ABG = {[k in 'a' | 'b' | 'c'] : k extends 'b' ? string : number}
// type ABC = {
// 	x : number;
// 	y : string;
// 	z : number;
// }

프로젝트 기반으로 해당 개념을 알아가 보자

특정 숫자에 따라 다른 SVG 컴포넌트를 보여주는 로직이 있다.

const resultTypeMap: { [key: number]: React.SVGProps<SVGSVGElement> } = {
    0: <FirstResultType />,
    36: <SecondResultType />,
    70: <ThirdResultType />,
    100: <ForthResultType />,
  };
return (
	<>
	<div ref={imageRef}>{resultTypeMap[temperature]}</div>
	....
	</>	
)

resultTypeMap의 키값에 따라 다른 컴포넌트를 보여주고자 할 때 생각나는 방법이 인덱스 시그니쳐 타입을 활용하면 될 것 같았다. 그래서 위와 같이 구현해 보았다. 하지만 위에서 알아봤듯, 인덱스 시그니처는 동적인 데이터에 타입을 지정해야 할 때 사용하는 것이 바람직하기에 타입을 변경해보려고 했다.

결과부터 보면, 아래와 같이 타입을 변경했다.

// test.type.tsx
export type TestFormType = {
  buddy: string;
  gender: string;
  age: string;
  trust: number;
  love: number;
  talk: number;
};

// 위에 있는 타입 기반으로 TestResultFormType을 만들었다. 
export type TestResultFormType = TestFormType & {
  id: number;
  temperature: 0 | 36 | 70 | 100;
  imageUrl: string;
  description: string;
  title: string;
  createdAt: string;
};

// TestResultFormType 타입의 temperature 타입의 값을 활용했다. 
const resultTypeMap: Record<TestResultFormType['temperature'], React.ReactNode> = {
    0: <FirstResultType />,
    36: <SecondResultType />,
    70: <ThirdResultType />,
    100: <ForthResultType />,
  };

return (
	<>
	<div ref={imageRef}>{resultTypeMap[temperature]}</div>
	....
	</>	
)

결과적으로 개선한 점을 찾아보면 기존의 인덱스 시그니처로 타입을 지정할 경우 키값이 number로 광범위했다. 하지만 Record 제네릭 타입을 활용하여 0,36,70,100을 제외한 값이 들어올 경우 에러를 통해 알려주도록 타입을 축소시킬 수 있었다. 즉 키의 타입을 체크할 수 있게 됐다.

결론

  1. 인덱스 시그니처는 동적 데이터를 위한 기능이며 런타임에도 객체의 속성을 알 수 없을때만 사용하는 것이 옳다.
  2. 인덱스 시그니처의 값 타입에 undefined를 추가하는 것을 고려해라. 왜냐하면 아무 값도 없는 것을 대비해서 에러를 알려주기 위해서이다.
  3. 가능하면 기본적인 타입 선언 및 Record 그리고 매핑된 타입 같은 정확한 타입을 사용하는 것이 좋다.

다시금 알게 된 것

  1. 자바스크립트의 강점은 객체를 생성하는 문법이 간단한 것.
profile
https://danny-blog.vercel.app/ 문제 해결 과정을 정리하는 블로그입니다.

0개의 댓글