인덱스 타입
타입스크립트의 인덱스 타입(Index Types) 은 객체 타입에서 특정 속성의 타입을 동적으로 추출하거나, 객체의 속성 이름을 타입으로 다룰 때 사용되는 고급 기능입니다. 이는 특히 매핑된 타입과 함께 사용될 때 강력한 시너지를 발휘하며, 런타임에 속성에 접근하는 자바스크립트의 동작을 타입 시스템에서 안전하게 표현할 수 있도록 돕습니다.
인덱스 타입은 크게 두 가지 주요 개념을 포함합니다. 인덱스 시그니처 (Index Signatures) 와 인덱스 접근 타입 (Indexed Access Types) 입니다.
인덱스 시그니처
인덱스 시그니처(Index Signatures) 는 객체가 가질 수 있는 속성들의 이름과 값의 타입을 미리 정의하지 않고, 속성 이름의 타입과 속성 값의 타입만으로 객체의 형태를 유연하게 정의할 때 사용됩니다. 이는 주로 객체의 속성 이름이 동적으로 결정되거나, 정해진 수의 속성이 아닌 임의의 속성들을 포함할 수 있는 경우에 유용합니다.
기본 문법은 다음과 같습니다.
interface MyObject {
[key: KeyType]: ValueType;
}
key
: 속성 이름을 나타내는 임의의 변수명 (관례적으로key
또는prop
사용).KeyType
: 속성 이름으로 허용될 수 있는 타입.string
,number
,symbol
또는 이들의 리터럴 타입, 유니온 타입만 가능합니다.ValueType
: 해당 속성 이름으로 접근했을 때 얻게 될 값의 타입.
예시를 통해 살펴보겠습니다.
// string 키를 가지며, 모든 값은 number 타입인 객체
interface StringNumberMap {
[key: string]: number;
}
const scores: StringNumberMap = {
"math": 90,
"english": 85,
"science": 92
};
console.log(scores["math"]); // 90
console.log(scores.english); // 85
// scores["korean"] = "bad"; // Error: 'string' 형식은 'number' 형식에 할당할 수 없습니다.
// number 키를 가지며, 모든 값은 boolean 타입인 배열과 유사한 객체
interface BooleanArrayLike {
[index: number]: boolean;
}
const checks: BooleanArrayLike = {
0: true,
1: false,
5: true // 중간 인덱스 건너뛰기 가능
};
console.log(checks[0]); // true
// console.log(checks["2"]); // Error: Element implicitly has an 'any' type because type 'BooleanArrayLike' has no index signature with a parameter of type '"2"'.
// string 인덱스는 number 인덱스 시그니처에 의해 허용되지 않습니다.
인덱스 시그니처의 주의사항
-
모든 속성 일치: 인덱스 시그니처를 정의하면, 해당 객체의 모든 명시적 속성도 인덱스 시그니처의 타입과 일치해야 합니다.
interface UserProfile { name: string; // 명시적 속성 [key: string]: string; // 인덱스 시그니처 (모든 string 키는 string 값을 가져야 함) } const user: UserProfile = { name: "Alice", city: "Seoul" // 'city'는 인덱스 시그니처에 의해 허용됨 }; // interface InvalidProfile { // id: number; // Error: Numeric index signature is missing. or string is not assignable to number // [key: string]: string; // } // 위와 같이 id: number는 string: string과 충돌하므로 오류 발생 // 만약 허용하려면 [key: string]: string | number; 와 같이 유니온으로 확장해야 합니다.
-
숫자 인덱스 시그니처:
[index: number]: ValueType
형태로 정의할 수 있으며, 이는 배열과 유사한 객체에 사용됩니다. 하지만 숫자 인덱스 시그니처를 정의하면 해당 객체의 string 인덱스도 숫자 인덱스 시그니처에 할당 가능해야 합니다. 이는 자바스크립트에서 숫자로 된 속성 이름이 내부적으로 문자열로 변환되기 때문입니다.interface NumberAndStringIndex { [key: number]: string; [key: string]: string; // string 인덱스 시그니처가 숫자 인덱스 시그니처를 포괄해야 함 }
인덱스 접근 타입
인덱스 접근 타입(Indexed Access Types / Lookup Types) 은 기존 객체 타입에서 특정 속성의 타입을 T[K]
문법을 사용하여 추출하는 기능입니다. 마치 자바스크립트에서 obj[key]
로 속성 값에 접근하듯이, 타입스크립트에서는 Type[Key]
로 속성의 타입에 접근합니다.
기본 문법은 다음과 같습니다.
type PropertyType = SomeObjectType[KeyType];
SomeObjectType
: 속성 타입을 추출할 대상 객체 타입 (인터페이스, 타입 별칭, 클래스 등).KeyType
: 추출하고자 하는 속성 이름의 타입. 이는string
리터럴,number
리터럴,symbol
리터럴, 또는keyof SomeObjectType
으로 얻어진 유니온 타입 등 속성 이름에 해당하는 타입이어야 합니다.
예시를 통해 살펴보겠습니다.
interface UserData {
id: number;
name: string;
email: string;
address: {
street: string;
city: string;
zipCode: string;
};
}
// 1. 특정 속성의 타입 추출
type UserId = UserData['id']; // type UserId = number
type UserName = UserData['name']; // type UserName = string
// 2. 중첩된 객체의 속성 타입 추출
type UserAddress = UserData['address']; // type UserAddress = { street: string; city: string; zipCode: string; }
type UserCity = UserData['address']['city']; // type UserCity = string
// 3. 유니온 타입을 사용하여 여러 속성들의 타입 유니온 추출
type UserContactInfo = UserData['email' | 'name']; // type UserContactInfo = string
// 4. 'keyof'와 함께 사용하기
// AllUserPropertyTypes는 UserData의 모든 속성 타입들의 유니온입니다.
type AllUserPropertyTypes = UserData[keyof UserData];
// type AllUserPropertyTypes = string | number | { street: string; city: string; zipCode: string; }
// 이 타입은 특정 속성 값을 가질 수 있음을 나타냅니다.
let value1: AllUserPropertyTypes = 123;
let value2: AllUserPropertyTypes = "some text";
let value3: AllUserPropertyTypes = { street: "Main St", city: "LA", zipCode: "90210" };
// let value4: AllUserPropertyTypes = true; // Error: 'boolean' 형식은 'AllUserPropertyTypes' 형식에 할당할 수 없습니다.
keyof
연산자와 인덱스 접근 타입을 결합하면 객체의 모든 속성 타입을 유니온으로 만들거나, 특정 타입 유틸리티를 구현할 때 매우 유용합니다.
인덱스 타입의 활용
인덱스 타입은 매핑된 타입과 함께 사용하여 복잡한 타입 변환 로직을 구현하는 데 핵심적인 역할을 합니다.
동적으로 속성 타입을 추출하는 함수 만들기
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const myUser = { id: 1, name: "Charlie", age: 40 };
const userName = getProperty(myUser, "name"); // userName의 타입은 string
const userId = getProperty(myUser, "id"); // userId의 타입은 number
console.log(userName); // Charlie
console.log(userId); // 1
// getProperty(myUser, "address"); // Error: Argument of type '"address"' is not assignable to parameter of type '"id" | "name" | "age"'.
이 함수에서 T[K]
는 obj[key]
가 반환하는 값의 타입을 정확하게 추론할 수 있게 해줍니다.
타입 안전한 Record/Dictionary 구현
type AppConfig = Record<string, string | number | boolean>;
const config: AppConfig = {
theme: "dark",
fontSize: 16,
enableLogging: true,
"api-key": "xyz123"
};
console.log(config.theme); // dark
console.log(config["fontSize"]); // 16
// console.log(config.nonExistent); // Property 'nonExistent' does not exist on type 'AppConfig'.
// 인덱스 시그니처만으로는 존재하지 않는 속성에 대한 접근 오류를 잡기 어려울 수 있지만,
// Record 유틸리티 타입은 이 경우에는 string 키만 허용하므로
// 명시되지 않은 속성에 대한 직접 접근을 막을 수 있습니다.
Record<K, T>
유틸리티 타입 자체가 [P in K]: T
형태의 매핑된 타입으로 구현되어 있으며, 인덱스 시그니처의 개념을 포함합니다.
인덱스 타입은 타입스크립트가 자바스크립트의 유연한 객체 구조를 정적 타입 시스템으로 안전하게 다룰 수 있도록 돕는 강력한 도구입니다. 인덱스 시그니처는 동적인 객체 형태를 정의할 때, 인덱스 접근 타입은 기존 객체 타입에서 특정 속성의 타입을 추출할 때 사용됩니다. 이 두 가지를 이해하고 적절히 활용하면, 복잡한 데이터 구조와 상호작용하는 코드를 더욱 타입 안전하고 견고하게 만들 수 있습니다.