인덱스 접근 타입은 JavaScript가 속성 접근에 쓰는 것과 같은 대괄호 문법으로 다른 타입에서 속성의 타입을 조회합니다. Person["age"]는 number이고, Person["address"]["city"]는 string입니다.
장점은 진실의 원천이 하나라는 것입니다. Age는 number의 두 번째 복사본이 아닙니다. Person["age"]가 string으로 바뀌면 Person["age"]로 쓴 모든 타입이 함께 바뀝니다. 인덱스 접근 타입은 컴파일 시점에만 존재하며 JavaScript를 출력하지 않습니다.
유니언 키와 T[keyof T]
인덱스는 키의 유니언일 수 있습니다. 결과는 속성 타입들의 유니언입니다.
Product[keyof Product]는 "Product의 아무 속성 값"을 타입 수준에서 말하는 방법입니다. 존재하지 않는 키를 요청하면 컴파일 오류입니다. Product["price"]는 error TS2339: Property 'price' does not exist on type 'Product'를 줍니다.
선택적 속성에는 undefined가 포함된다
strictNullChecks(strict에서 켜짐)가 켜져 있으면 선택적 속성의 타입에 undefined가 포함되고, 그 인덱스 접근 타입도 마찬가지입니다.
type Profile = { name: string; nickname?: string };
type Nick = Profile["nickname"]; // string | undefined
undefined를 빼려면 감싸세요. NonNullable<Profile["nickname"]>은 string입니다.
T[number]로 배열 요소 타입 얻기
배열은 숫자로 인덱싱되므로, 배열 타입을 number로 인덱싱하면 요소 타입을 얻습니다. 여러 단계를 거쳐서도 동작합니다.
생성된 API 클라이언트처럼 직접 제어할 수 없는 곳에서 온 타입일 때 유용합니다. ApiResponse["data"]["users"][number]는 아무도 export하지 않은 사용자 타입에 이름을 붙여 줍니다.
튜플: 위치로 인덱싱하기
튜플 타입은 특정 위치로, 모든 요소 타입의 유니언을 위한 number로, 그리고 길이를 리터럴 타입으로 얻는 "length"로 인덱싱할 수 있습니다.
(typeof arr)[number]: const 배열에서 유니언 만들기
실제로 가장 흔한 용도입니다. 허용되는 값의 목록을 배열로 두고(런타임에 순회할 수 있도록), 그 배열에서 유니언 타입을 끌어냅니다(컴파일러가 검사하도록).
두 부분이 이를 가능하게 합니다. as const는 배열을 리터럴 타입의 readonly 튜플로 만듭니다. 없으면 ROLES는 string[]이고 (typeof ROLES)[number]는 그냥 string입니다. 그다음 typeof가 값을 타입으로 바꾸고, [number]가 그 요소들을 유니언으로 모읍니다. 괄호는 선택 사항이지만(typeof ROLES[number]도 같은 뜻), 연산 순서를 분명하게 해 줍니다.
문자열이 배열에 있는지 확인하기
이 readonly 튜플에는 의외의 점이 하나 있습니다. includes 메서드가 요소 타입만 받으므로 임의의 문자열을 넘길 수 없습니다.
index.ts(6,25): error TS2345: Argument of type 'string' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
검사할 때는 배열을 readonly string[]로 넓히고, 검사에 성공하면 값이 좁혀지도록 함수를 타입 가드로 만드세요.
이 패턴은 두 가지 일에 목록 하나를 씁니다. 런타임 검사는 배열을 쓰고, 타입은 그 배열에서 끌어내므로 둘이 어긋날 수 없습니다.
제네릭의 인덱스 접근: T[K]
제네릭 안에서 K extends keyof T와 함께 쓰는 T[K]는 호출하는 쪽이 고른 속성의 타입입니다.
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 28 },
];
const names = pluck(users, "name"); // string[]
const ages = pluck(users, "age"); // number[]
K extends keyof T 제약이 T[K]를 합법으로 만듭니다. 이것이 없으면 TypeScript는 K가 T의 키라는 것을 알 수 없어서 error TS2536: Type 'K' cannot be used to index type 'T'를 보고합니다.
문법 한눈에 보기
| 쓴 것 | 의미 | 결과의 예 |
|---|---|---|
T["key"] | 속성 하나의 타입 | Person["age"]는 number |
T["a" | "b"] | 그 속성 타입들의 유니언 | string | number |
T[keyof T] | 모든 속성 타입의 유니언 | 모든 값 타입 |
T["a"]["b"] | 중첩 속성 | Person["address"]["city"] |
Arr[number] | 배열 요소 타입 | User[]는 User |
Tup[0] | 특정 위치의 튜플 요소 | 첫 번째 요소 타입 |
Tup["length"] | 리터럴로 된 튜플 길이 | 3 |
(typeof arr)[number] | const 배열 값들의 유니언 | "admin" | "editor" |
T[typeof key] | 상수의 타입으로 인덱싱 | T["name"]과 같음 |
인덱스는 항상 타입입니다. key가 const 변수일 때 Person[key]는 두 오류로 실패합니다: error TS2538: Type 'key' cannot be used as an index type.와 error TS2749: 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?.
자주 묻는 질문
TypeScript에서 인덱스 접근 타입이란 무엇인가요?
타입 T에서 속성 K의 타입을 조회하는 T[K] 형태의 타입입니다. type Person = { name: string; age: number }라면 Person["age"]는 number입니다. JavaScript의 속성 접근과 같은 대괄호 문법을 쓰지만, 컴파일 시점에 타입에 대해 동작합니다.
TypeScript에서 배열 요소의 타입은 어떻게 얻나요?
배열 타입을 number로 인덱싱하세요. type Users = User[]라면 Users[number]는 User입니다. 배열 값이라면 typeof와 조합합니다: (typeof users)[number].
문자열 배열을 유니언 타입으로 바꾸려면 어떻게 하나요?
TypeScript가 리터럴 값을 유지하도록 배열을 as const로 선언한 다음 number로 인덱싱하세요. const roles = ["admin", "editor"] as const; type Role = (typeof roles)[number];은 "admin" | "editor"가 됩니다. as const가 없으면 요소 타입은 그냥 string입니다.
인덱스 접근 타입에서 변수를 인덱스로 쓸 수 없는 이유는 무엇인가요?
인덱스는 타입이어야 합니다. const key = "name"일 때 Person[key]는 key가 값이므로 실패합니다(오류 TS2538과 TS2749). Person[typeof key]라고 쓰거나, 리터럴을 직접 쓰세요: Person["name"].
T[keyof T]는 무슨 뜻인가요?
T를 모든 키의 유니언으로 인덱싱하므로, 결과는 모든 속성 타입의 유니언입니다. { a: string; b: number }라면 string | number입니다. "이 객체의 아무 값"을 타입 수준에서 말하는 방법입니다.