인덱스 시그니처란?
인덱스 시그니처는 객체의 속성 이름을 미리 정의하지 않고, 동적으로 속성에 접근할 수 있도록 하는 TypeScript 문법입니다.
interface StringDictionary {
[key: string]: string;
}
기본 문법
{
[key: KeyType]: ValueType;
}
- key: 변수명 (관례적으로 key, index, prop 등을 사용)
- KeyType:
string
,number
,symbol
중 하나 - ValueType: 해당 키로 접근했을 때 반환될 값의 타입
실제 사용 예시
1. 기본 사용법
interface UserDatabase {
[userId: string]: {
name: string;
age: number;
};
}
const users: UserDatabase = {
"user001": { name: "김철수", age: 25 },
"user002": { name: "이영희", age: 30 },
"user003": { name: "박민수", age: 28 }
};
console.log(users["user001"].name); // "김철수"
2. API 응답 처리
interface ApiResponse {
[endpoint: string]: unknown;
}
const response: ApiResponse = {
"/users": [{ id: 1, name: "John" }],
"/posts": [{ id: 1, title: "Hello" }],
"/comments": []
};
3. 환경 변수 타입 정의
interface EnvironmentVariables {
[key: string]: string | undefined;
}
const env: EnvironmentVariables = {
NODE_ENV: "production",
API_KEY: "abc123",
PORT: "3000"
};
키 타입별 특징
string 키
interface StringIndex {
[key: string]: number;
}
const scores: StringIndex = {
math: 95,
english: 88,
science: 92
};
number 키
interface NumberIndex {
[index: number]: string;
}
const colors: NumberIndex = {
0: "red",
1: "green",
2: "blue"
};
// 배열과 비슷하게 동작
console.log(colors[0]); // "red"
symbol 키 (TypeScript 4.4+)
interface SymbolIndex {
[key: symbol]: string;
}
const sym1 = Symbol("id");
const obj: SymbolIndex = {
[sym1]: "unique-value"
};
값 타입 지정
any vs unknown
// 권장하지 않음 - 타입 안정성 없음
interface UnsafeObject {
[key: string]: any;
}
// 권장 - 타입 가드 필요
interface SafeObject {
[key: string]: unknown;
}
const obj: SafeObject = { data: 123 };
// 타입 가드 사용
if (typeof obj.data === "number") {
console.log(obj.data + 1); // OK
}
Union 타입 사용
interface MixedData {
[key: string]: string | number | boolean;
}
const config: MixedData = {
host: "localhost",
port: 3000,
ssl: true
};
고정 속성과 함께 사용
interface UserConfig {
// 고정 속성
username: string;
email: string;
// 인덱스 시그니처
[key: string]: string | number | boolean;
}
const config: UserConfig = {
username: "john_doe",
email: "john@example.com",
theme: "dark",
notifications: true,
maxRetries: 3
};
주의사항: 인덱스 시그니처의 값 타입은 고정 속성의 타입을 포함해야 합니다.
// ❌ 에러 발생
interface InvalidConfig {
username: string;
[key: string]: number; // username은 string인데 number만 허용
}
// ✅ 올바른 예시
interface ValidConfig {
username: string;
[key: string]: string | number;
}
실전 활용 패턴
1. 다국어 지원
interface Translations {
[key: string]: {
[language: string]: string;
};
}
const i18n: Translations = {
greeting: {
ko: "안녕하세요",
en: "Hello",
ja: "こんにちは"
},
farewell: {
ko: "안녕히 가세요",
en: "Goodbye",
ja: "さようなら"
}
};
2. 폼 데이터 처리
interface FormData {
[fieldName: string]: string | number | File;
}
function handleFormSubmit(data: FormData) {
Object.keys(data).forEach(key => {
console.log(`${key}: ${data[key]}`);
});
}
3. 캐시 구현
interface Cache<T> {
[key: string]: {
value: T;
timestamp: number;
};
}
const imageCache: Cache<string> = {
"profile.jpg": {
value: "base64encodedstring...",
timestamp: Date.now()
}
};
제네릭과 함께 사용
interface Dictionary<T> {
[key: string]: T;
}
const stringDict: Dictionary<string> = {
name: "John",
city: "Seoul"
};
const numberDict: Dictionary<number> = {
age: 30,
score: 95
};
const userDict: Dictionary<{ name: string; age: number }> = {
user1: { name: "Alice", age: 25 },
user2: { name: "Bob", age: 30 }
};
Record 유틸리티 타입 사용
TypeScript는 인덱스 시그니처를 간단하게 표현할 수 있는 Record
유틸리티 타입을 제공합니다.
// 인덱스 시그니처 방식
interface Dictionary1 {
[key: string]: number;
}
// Record 유틸리티 타입 (동일한 효과)
type Dictionary2 = Record<string, number>;
// 사용 예시
const scores: Record<string, number> = {
math: 95,
english: 88
};
주의사항 및 제한사항
1. 키 타입 제한
// ❌ 에러: 키 타입은 string, number, symbol만 가능
interface Invalid {
[key: boolean]: string; // Error!
}
2. 선택적 속성과의 충돌
interface Problem {
name?: string;
[key: string]: string; // Error: name은 string | undefined
}
// 해결 방법
interface Solution {
name?: string;
[key: string]: string | undefined;
}
3. 타입 안정성 저하
interface Loose {
[key: string]: any;
}
const obj: Loose = { name: "John" };
obj.nonExistent.method(); // 런타임 에러! 컴파일 시 감지 안됨
대안: Mapped Types
더 엄격한 타입 안정성이 필요하다면 Mapped Types를 고려하세요.
type AllowedKeys = "name" | "age" | "email";
type StrictObject = {
[K in AllowedKeys]: string;
};
const user: StrictObject = {
name: "John",
age: "30",
email: "john@example.com"
// invalid: "not allowed" // Error!
};
마무리
인덱스 시그니처는 동적 속성을 다룰 때 매우 유용하지만, 타입 안정성이 약해질 수 있습니다.
사용 시 권장사항:
- 가능하면
unknown
을 사용하고 타입 가드 활용 - 속성 이름을 알 수 있다면 명시적 인터페이스 정의 선호
Record
유틸리티 타입으로 간결하게 표현- 필요한 경우 Mapped Types로 더 엄격한 타입 정의
적재적소에 인덱스 시그니처를 활용하여 유연하면서도 안전한 TypeScript 코드를 작성하세요! 🚀