아이템 15. 동적 데이터에 인덱스 시그니처 사용하기

아이템 15. 동적 데이터에 인덱스 시그니처 사용하기

아이템 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
키마다 다른 타입을 가질 수 없음
- thrustnumber 로 사용하고 싶은데, 그럴 수 없음
(예시 : 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, 매핑된 타입과 같은 (인덱스 시그니처보다)‘정확한 타입’을 사용하는 것이 좋다.