icon
5장 : 고급 타입

인덱스 타입


타입스크립트의 인덱스 타입(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 형태의 매핑된 타입으로 구현되어 있으며, 인덱스 시그니처의 개념을 포함합니다.


인덱스 타입은 타입스크립트가 자바스크립트의 유연한 객체 구조를 정적 타입 시스템으로 안전하게 다룰 수 있도록 돕는 강력한 도구입니다. 인덱스 시그니처는 동적인 객체 형태를 정의할 때, 인덱스 접근 타입은 기존 객체 타입에서 특정 속성의 타입을 추출할 때 사용됩니다. 이 두 가지를 이해하고 적절히 활용하면, 복잡한 데이터 구조와 상호작용하는 코드를 더욱 타입 안전하고 견고하게 만들 수 있습니다.