인덱스 시그니처(Index Signature)는 속성 이름을 미리 다 알 수는 없지만 값의 모양은 알 때, [key: string]: T처럼 "어떤 키로 꺼내든 값은 T"라고 적는 TypeScript 문법이다. 키를 모르는 사전(dictionary) 모양의 객체를 표현할 때 쓴다.
interface Scores {
[name: string]: number
}
const scores: Scores = { kim: 90, lee: 85 }
scores['park'] = 70 // 어떤 문자열 키든 된다- 키 타입으로는
string,number,symbol, 템플릿 문자열 패턴(`data-${string}`), 그리고 이들로만 이뤄진 유니언을 쓸 수 있다(템플릿 리터럴 타입) number인덱서와string인덱서를 함께 두면, 숫자 인덱서의 값 타입은 문자열 인덱서 값 타입의 하위 타입이어야 한다. 자바스크립트는 숫자 키를 문자열로 바꿔 찾기 때문이다(obj[100]은obj["100"]과 같다)
이름 붙은 속성도 규칙을 따른다
문자열 인덱스 시그니처는 obj.prop을 obj["prop"]으로도 읽을 수 있다는 뜻이다. 그래서 같은 타입에 적은 이름 붙은 속성도 인덱스 시그니처의 값 타입에 맞아야 한다.
interface Dict {
[key: string]: number
length: number // OK
name: string // 에러: string은 number에 맞지 않는다
}
interface Mixed {
[key: string]: number | string
length: number // OK
name: string // OK: 값 타입이 유니언이면 함께 둘 수 있다
}readonly 인덱스 시그니처
앞에 readonly를 붙이면 인덱스로 값을 읽기만 할 수 있고 대입은 에러가 된다.
interface ReadonlyScores {
readonly [name: string]: number
}
declare const s: ReadonlyScores
s['kim'] = 100 // 에러: 읽기만 허용된다없는 키를 읽을 때
선언하지 않은 키도 인덱스 시그니처 덕분에 값 타입 그대로(number) 취급된다. 실제로 없는 키를 읽으면 런타임에는 undefined가 나온다. noUncheckedIndexedAccess 옵션을 켜면 선언하지 않은 필드를 읽은 결과에 undefined가 더해져 검사를 강제할 수 있다(tsconfig 핵심 옵션, 타입 좁히기와 타입 가드).
관련 문법
- 매핑된 타입(
{ [K in Keys]: T })은 인덱스 시그니처 문법을 바탕으로, 정해진 키 목록을 돌며 타입을 만든다 - 키와 값 타입으로 객체 타입을 만드는 유틸리티 타입
Record<Keys, Type>도 있다. 사전 모양은Record<string, number>처럼 적을 수도 있다
출처: TypeScript Handbook - Index Signatures · TSConfig - noUncheckedIndexedAccess · Mapped Types · Utility Types - Record