※ 아이템15 동적 데이터에 인덱스 시그니처 사용하기
타입스크립트에서는 타입에 인덱스 시그니처 를 명시해 매핑을 유연하게 표현한다.
type Rocket = {[property: string]: string};
const rocket: Rocket = {
name: 'Falcon 9',
variant: 'v1.0',
thrust: '4,940 kN',
};
위 코드에서 인덱스 시그니처 [property: string]: string 는 다음 의미를 갖고 있다.
- 키의 이름 : 키의 위치만 표시하는 용도
- 키의 타입 : string | number | symbol
- 값의 타입 : 어떤것이든 될 수 있음
▷ 위와 같이 타입 체크가 수행되었을 때의 단점
- 잘못된 키를 포함해 모든 키를 허용한다
const rocket: Rocket = {
Name: 'Falcon 9', //name 대신 Name으로 작성해도 유효하다
variant: 'v1.0',
thrust: '4,940 kN',
};
- 특정키가 필요하지 않다
const rocket1: Rocket = {} //정상
- 키마다 다른 타입을 가지는 것이 불가하다
const rocket: Rocket = {
Name: 'Falcon 9',
variant: 'v1.0',
thrust: 4940,
};
- 키는 무엇이든 가능하기 때문에 자동완성 기능이 동작하지 않는다
인덱스 시그니처를 인터페이스로 변경하면 단점들을 보완하고 타입스크립트에서 제공하는 자동완성, 정의로 이동, 이름바꾸기 등의 언어 서비스를 모두 사용할 수 있게 된다.
interface Rocket {
name: string;
variant: string;
thrust_kN: number;
}
const falconHeavy: Rocket = {
name: 'Falcon',
variant: 'v1.0',
thrust_kN: 15_200
}
▷ 그렇다면 언제 인덱스 시그니처를 사용해야할까 ?
동적 데이터를 표현할 때 사용한다. 동적 속성을 포함하는 객체를 다루는 경우, 타입스크립트는 해당 속성의 타입을 정적으로 파악할 수 없기 때문에 타입검사를 수행할 수 없다. 따라서 이러한 경우 인덱스 시그니처를 사용해서 해당 속성의 타입을 정의할 수 있다.
ex ) 데이터 행을 열 이름과 값으로 매핑하는 개체로 나타내고 싶은 경우
function parseCSV(input: string):{[columnName: string]: string}[]{
const lines = input.split('\n'); //CSV 문자열 분할
const [header, ...rows] = lines; // 헤더행 추출
const headerColumns = header.split(','); // 헤더행의 각 열 이름 추출
return rows.map(rowStr => {
const row: {[columnName: string]: string} = {}; //빈 객체 row
rowStr.split(',').forEach((cell,i)=>{
row[headerColumns[i]]=cell; //열이름과 값을 매핑해서 row 객체에 저장
});
return row; //rows 배열에 추가됨
});
}
const inputData = `name,age,city
Alice,25,New York
Bob,30,London
Charlie,35,Paris`;
const result = parseCSV(inputData);
//[
// { name: 'Alice', age: '25', city: 'New York' },
// { name: 'Bob', age: '30', city: 'London' },
// { name: 'Charlie', age: '35', city: 'Paris' }
//]
이와 같이 런타임때까지 객체의 속성을 알 수 없을 경우에만 인덱스 시그니처를 사용하도록 한다
타입에 가능한 필드가 제한되어 있는 경우에는 인덱스 시그니처로 모델링 하지 않는다
interface Row1 {[column: string]: number} //광범위하다
▶ 인덱스 시그니처의 대안
1. Record
- 키 타입에 유연성을 제공하는 제너릭 타입
- string 의 부분집합을 사용할 수 있다
Record<K, T> // K : 속성 이름의 타입, T : 속성 값의 타입
type Vec3D = Record<'x' | 'y' | 'z', number>; //Vec3D 객체의 속성 이름은 x,y,z 이고, 값은 number 타입이다
2. 매핑된 타입
- 키마다 별도의 타입을 사용하게 해준다
type ABC = {[k in 'a' | 'b' | 'c']: k extends 'b' ? string : number};
※ 아이템16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike 를 사용하기
자바스크립트의 객체 : 키, 값 쌍의 모음
- 키는 보통 문자열이다
- 값은 어떤것이든 될 수 있다
- 해시가능 객체라는 표현이 없음 (파이썬, 자바 有)
- 해시가능객체 ? 해시 함수를 사용해서 해시 테이블 같은 자료구조에서 Key 로 사용할 수 있는 객체이다. 해시 가능한 객체는 대표적으로 String, Integer, Double 등이 있다.
>{1: 2, 3: 4}
{'1':2, '3':4}
속성 이름으로 숫자를 사용하려고하면 자바스크립트 런타임은 문자열로 변환한다
x = [] // 배열 선언
x = [1,2,3] // 배열 할당
x[0] // 호출 : 1
x['1'] // 호출 : 2
Object.keys(x) // ['0','1','2']
문자열을 사용해도 배열에 접근할 수 있고, 배열의 Key 를 나열하면 키가 문자열로 출력된다
타입스크립트는 위와 같은 혼란을 바로잡기 위해 숫자키를 허용한다. 런타임에는 문자열 키로 인식하지만, 타입체크시점에 오류를 잡을 수 있다.
interface Array<T>{
[n: number] : T;
}
const xs = [1,2,3];
const keys = Object.keys(xs); //타입 : string[]
for (const key in xs){ // for-in
key; // '1' 타입 : string
const x = xs[key]; //xs[1] : 2
}
Object.keys 는 여전히 문자열로 반환한다. 마지막 줄에서는 string 이 number 에 할당되고있다 (실용적인 허용으로, 좋은 배열을 순회하는데 좋은 방법은 아닌다)
//인덱스 타입이 중요하지 않다면 for-of
for (const x of xs){ // xs 요소들을 반복하면서 x 에 할당
x;
}
//인덱스의 타입이 중요하다면 forEach
xs.forEach((x,i)=>{
i; // 현재 요소의 인덱스 : number
x; // x로 할당됨 : number
});
//중간에 멈춰야한다면
for(let i=0; i<xs.length; i++){
const x = xs[i];
if(x<0) break;
}
타입이 불확실할 때 for in 루프는 for-of, C 스타일 for 에 비해 느리다.
인덱스 시그니처가 number 로 표현되어있다면 입력한 값이 number 여야 하지만 실제 런타임에서는 string 이다
const row: {[columnNum: number]: number}
number 를 인덱스 타입으로 사용하면 숫자 속성이 의미를 지닌다는 오해를 불러일으킬 수 있으니
숫자를 사용해 인덱스할 항목을 지정할 땐 Array 또는 튜플 타입을 사용한다
const row: number[] = [1, 2, 3];
type Row = [number, number, number];
const row: Row = [1, 2, 3];
배열과 비슷한 형태의 튜플을 사용하고 싶다면 ArrayLike 타입을 사용한다
function checkedAccess<T>(xs: ArrayLike<T>, i: number): T {
if (i < xs.length) {
return xs[i];
}
throw new Error(`배열의 끝을 지나서 ${i}를 접근하려고 했습니다`);
}
ArrayLike 는 배열과 비슷한 객체를 나타내는 타입으로, length 와 인덱스를 가지고있다
interface ArrayLike<T> {
readonly length: number; //객체의 요소 갯수
readonly [n: number]: T; // 인덱스 시그니처를 통해 해당 위치 요소값을 가져옴
}
요약
- 배열은 객체이므로 키는 수자가 아니라 문자열이다. 인덱스 시그니처로 사용된 number 타입은 버그를 잡기 위한 순수 타입스크립트 코드이다.
- 인덱스 시그니처에 number 를 사용하기 보다 Array 나 튜플, 또는 ArrayLike 타입을 사용하는 것이 좋다
'Typescript' 카테고리의 다른 글
[Typescript] 이펙티브 타입스크립트 3 아이템19-20 (0) | 2023.03.09 |
---|---|
[Typescript] 이펙티브 타입스크립트 2장 아이템17-18 (0) | 2023.03.07 |
[Typescript] 이펙티브 타입스크립트 2장 아이템14 (0) | 2023.03.05 |
[Typescript] 이펙티브 타입스크립트 2장 아이템13 (0) | 2023.03.05 |
[Typescript] 이펙티브 타입스크립트 2장 아이템10-12 (0) | 2023.02.27 |