카탈로그🔖 : 타입 시스템 > 동적 데이터에 인덱스 시그니처 사용하기
매번 타입 스크립트를 사용하면서 객체의 키값 활용하여 값을 가지고 올 때 문제가 발생 해왔고 이를 해결하기 위해 인덱스 시그니처를 활용하여 객체 프로퍼티의 값을 가지고 왔는데 이 부분에 대해 깊이 알고 싶어 정리하고자 한다. 우선 타입 스크립트에서 인덱스 시그니처가 무엇이고 어떻게 사용하는 것인지 살펴보고 더 나은 타입을 적용할 수 있는지 고민해보려고 한다.
인덱스 시그니처는 { [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}
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. 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을 제외한 값이 들어올 경우 에러를 통해 알려주도록 타입을 축소시킬 수 있었다. 즉 키의 타입을 체크할 수 있게 됐다.