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


인덱스 타입은 타입스크립트가 자바스크립트의 유연한 객체 구조를 정적 타입 시스템으로 안전하게 다루도록 돕는 강력한 도구입니다.

인덱스 시그니처는 동적인 객체 형태를 정의할 때, 인덱스 접근 타입은 기존 객체 타입에서 특정 속성 타입을 추출할 때 사용됩니다.

이 둘을 함께 이해하고 적절히 활용하면 복잡한 데이터 구조와 상호작용하는 코드를 더 타입 안전하고 견고하게 만들 수 있습니다.

목차