프로젝트에서 공공API를 통해 레시피를 가져오는데 데이터가
이런식으로 되어있었다.
MANUAL01 ~ MANUAL20까지 Key가 존재한다는 것은 알고 있으나 해당 메뉴에 메뉴얼이 몇 개 있는지는 모르는 상태이므로 우선 01부터 20까지 하나하나 확인해주는 코드를 작성했다.
import type { RecipeData } from "@/types/recipe.types";
const manualFilter = (recipe: RecipeData) => {
const manuals = [];
for (let i = 1; i <= 20; i++) {
const manualNumber = `MANUAL${i.toString().padStart(2, "0")}`;
const manual = recipe[manualNumber];
console.log("manual: ", manual);
if (manual === "") {
break;
}
manuals.push(manual);
}
return manuals;
};
export default manualFilter;
그런데
const manual = recipe[manualNumber];
에서 RecipeData 형식에서 string 형식의 매개 변수인 manualNumber 인덱스 시그니처를 찾을 수 없다고 오류가 발생했다.
manualNumber는 MANUAL01 ~ MANUAL20까지 불러내기 위한 변수이기 때문에 당연히 RecipeData 형식에는 존재하지 않는다.
그렇다면 없는 Key를 어떻게 불러와야할까?
이러한 경우에는 인덱스 시그니처를 이용할 수 있다.
RecipeData에 [key: string] : string 을 추가하면 해결된다!
[property: string] : string
1. 키의 이름 : 타입 체커에서는 사용하지 않는다
2. 키의 타입 : string / number / symbol 의 조합이여야하지만 보통은 string 을 사용
3. 값의 타입 : 어떤 것이든 될 수 있다.
하지만 타입 체크가 실행되면 몇가지 단점이 나타난다.
- 잘못된 키를 포함해 모든 키를 허용한다.
의도하지 않은 프로퍼티를 사용해도 타입 체커가 오류를 잡지 못한다.
age 대신 Age를 사용해도 문제가 발생하지 않는다. - 특정 키가 필요하지 않다.
{}도 유효한 값이 될 수 있다. - 키마다 다른 타입을 가질 수 없다.
모든 키가 string이 아니라 number가 될 수도 있는데 수정하거나 지정할 수 없다. - 자동 완성 기능을 사용할 수 없다.
객체의 속성을 확인하기 어렵다.
이러한 단점으로 인해 객체의 특정 속성에 대해 명시성이 감소하여 다른 개발자가 봤을 때 이해하기 어려울 수 있으며 엄격한 타입 체크 불가능으로 의도치 않은 동작을 수행하여 버그가 발생할 가능성이 높다.
결론적으로 인덱스 시그니처는 부정확하므로 타입스크립트의 장점을 최대한 활용하기 어렵다.
명시적으로 키의 목록을 정의하기 어려운 경우에만 사용해야 한다.
그렇다면 인덱스 시그니처의 대안이 무엇이 있을까?
1. Record 사용
특정 타입을 기반으로 모든 키에 동일한 값을 갖는 객체를 생성하는 타입이다.
type A = Record<string, number>;
type Vec3D = Record<'x' | 'y' | 'z', number>;
Record<키의 타입, 값의 타입> 형태로 사용된다.
인덱스 시그니처와 달리 타입만 명시적으로 선언한다.
2. 매핑된 타입 사용
type Vec3D = {[k in 'x' | 'y' | 'z' ]: number};
type ABC = {[k in 'a' | 'b' | 'c' ]: k extends 'b' ? string : number};
키마다 별도의 타입을 선언할 수 있다.
프로젝트에서 RecipeData의 모든 키는 string으로 되어있고 값 또한 string이기에 굳이 각 키마다 별도의 타입을 선언할 필요가 없어서 Record를 사용하였다.
type RecipeData = Record<string, string>;
const manualFilter = (recipe: RecipeData) => {
const manuals = [];
for (let i = 1; i <= 20; i++) {
const manualNumber = `MANUAL${i.toString().padStart(2, "0")}`;
const manual = recipe[manualNumber];
console.log("manual: ", manual);
if (manual === "") {
break;
}
manuals.push(manual);
}
return manuals;
};
export default manualFilter;
하지만 이렇게 하면
recipeInfo: manualFilter(recipe),
manualFilter 함수에 recipe 매개변수를 전달하여 recipeInfo에 그 결과를 할당하는 코드에서 RecipeData 형식에 인덱스 시그니처 유형인 string이 없다고 오류가 뜬다
그래서...
export interface RecipeData {
[key: string]: string;
ATT_FILE_NO_MAIN: string;
...
}
어쩔 수 없이 인덱스 시그니처를 사용하긴 했으나 대안법을 찾아봐야겠다.
--- 추가 : 시니어 개발자분께 여쭤봤지만 현재로는 인덱스 시그니처를 사용하는 게 최선이라는 답변을 얻었다.
'프로젝트 > Resthub' 카테고리의 다른 글
[Resthub#개발일지7] 이미지 늦게 로딩되는 현상 해결 (0) | 2024.02.02 |
---|---|
[Resthub#개발일지6] React에서 공공데이터 API 가져오기 (+전처리까지) (1) | 2024.01.21 |
[Resthub#개발일지4] 전역 상태 관리 Context API를 Zustand로 변경하다 (0) | 2024.01.08 |
[Resthub#개발일지3] Firebase의 구글 Login & Logout 기능 구현 (0) | 2024.01.04 |
[Resthub#개발일지2] Firebase와 React 연동하기 (1) | 2023.12.31 |