아이템 15. 동적 데이터에 인덱스 시그니처 사용하기 #
// JS
const rocket = {
name: 'Falcon 9',
variant: 'v1.0',
thrust: '4,940 kN',
}
// TS (인덱스 시그니처 사용 예시)
// Rocket = {[property: string]: string};
const rocket: Rocket = {
name: 'Falcon 9',
variant: 'v1.0',
thrust: '4,940 kN',
}
위 예시에서 {[property: string]: string}
부분이 인덱스 시그니처이다.
**세 가지 기능을 한다.**
기능 | 설명 |
---|---|
키의 이름 | 키의 위치만 표시하는 용도 (* 타입 체커에서는 사용하지 않는다.) |
키의 타입 | string, number, symbol 의 조합 (* 보통은 string 을 사용한다.) |
값의 타입 | 어떤 것이든 가능 |
단, 아래와 같은 특징(단점, 커버할 수 없는 것들)이 있다.
설명 |
---|
잘못된 키를 포함, 모든 키를 허용 - name 대신 Name 으로 작성해도 OK |
특정 키가 필요하지 않음 - {} 도 OK |
키마다 다른 타입을 가질 수 없음 - thrust 를 number 로 사용하고 싶은데, 그럴 수 없음 |
(예시 : name: 을 입력할 때 )키는 무엇이든 가능하기 때문에 자동 완성 기능 활용 X |
인덱스 시그니처는 동적 데이터를 표현할 때 사용할 수 있다. #
function parseCSV(input: string): {[columnName: string]: string}[] {
const lines = input.split('\n');
const [header, ..., rows] = lines;
const headerColumns = header.split(',');
return rows.map(rowStr => {
const row: {[columnName: string]: string} = {};
rowStr.split(',').forEach((cell, i) => {
row[headerColumns[i]] = cell;
});
return row;
});
}
// 혹은 (string 을 보장할 수 없다면 undefined 를 추가할 수 있다.)
function safeParseCsv(input: string): {[columnName: string]: string | undefined}[] {
return parseCsv(input);
}
위와 같이 (업로드된 파일을 파싱하는 등의)일반적인 상황에서 ‘열’의 이름(header)이 무엇인지 미리 알 방법은 없다.
이럴 때는 ‘인덱스 시그니처’를 사용할 수 있다.
반면에 이미 ‘열’의 이름을 알고 있다면 인터페이스와 같은 것들을 사용하는 것이 훨씬 좋다.
Map, Record, 매핑된 타입, interface 등을 사용할 수 있다. #
Map
연관배열의의 경우 객체에 인덱스 시그니처를 사용하는 대신 Map 타입을 사용할 수 있다.
Interface
interface Vec3D {
x: number;
y: string;
x: number;
}
Record
type Vec3D = Record<'x' | 'y' | 'z', number>;
// Type Vec3D = {
// x: number;
// y: number;
// z: number;
// }
매핑된 타입
type Vec3D = {[k in 'x' | 'y' | 'z']: number};
// Type Vec3D = {
// x: number;
// y: number;
// z: number;
// }
type Vec3D = {[k in 'x' | 'y' | 'z']: k extends 'y' ? string : number};
// Type Vec3D = {
// x: number;
// y: string;
// z: number;
// }
결론 : 인덱스 시그니처는 부정확하므로 더 나은 방법(interface, Record, 매핑된 타입, …) 사용을 권장한다. #
// 예시 : interface
// 키마다 다른(다양한) 타입을 사용할 수 있다.
interface Rocket {
name: string;
variant: string;
thrust_kN: number;
}
const rocket: Rocket = {
name: 'Falcon heavy',
variant: 'v1',
thrust_kN: 15200,
}
요약 & 정리 #
- 런타임 때까지 객체의 속성을 알 수 없을 경우(예를 들어, 파일 업로드 후 파싱하는 경우)에만 ‘인덱스 시그니처’를 사용한다.
- 속성을 임의로 추가할 수 없으니까!! (by 잉여 속성 체크)
- 값이 있음을 보장할 수 없다면
undefined
를 사용할 수 있다. - 가능하다면 인터페이스,
Record
, 매핑된 타입과 같은 (인덱스 시그니처보다)‘정확한 타입’을 사용하는 것이 좋다.