
타입스크립트를 처음 배울 때는 타입을 선언하는 것에 집중하게 된다.
string, number, 인터페이스를 정의하고, 함수의 파라미터와 반환값에 붙이는 식으로 진행한다.
타입을 선언하는 것을 넘어서, 기존 타입으로부터 새로운 타입을 만들어내는 방법도 존재한다.
인덱스드 액세스 타입
타입 내부의 값을 꺼내는 방법이다.
객체 타입에서 특정 프로퍼티의 타입만 추출하고 싶을 때 사용한다.
마치 자바스크립트에서 객체의 값을 obj["key"]로 접근하듯, 타입도 같은 문법으로 내부 타입을 꺼낼 수 있다.
type Post = {
title: string;
content: string;
author: {
id: number;
name: string;
};
};
type AuthorType = Post["author"];
// 결과: { id: number; name: string; }
type AuthorIdType = Post["author"]["id"];
// 결과: number
여기서 "author"처럼 대괄호 안에 들어가는 문자열 리터럴을 인덱스라고 부르고, 이 방식 전체를 인덱스드 액세스 타입이라고 한다.
한 가지 주의할 점이 있다. 인덱스 자리에는 반드시 타입이 와야 한다. 변수를 선언해서 넣으면 작동하지 않는다.
const key = "author";
type Wrong = Post[key]; // ❌ 오류 — key는 값이지 타입이 아님
type Correct = Post["author"]; // ✅
배열 타입에서 사용할 때
배열 타입에서는 조금 다르게 사용한다.
아래처럼 배열 타입이 있을 때, [number] 인덱스를 사용하면 배열 요소의 타입을 추출할 수 있다.
type PostList = {
title: string;
content: string;
author: {
id: number;
name: string;
};
}[];
type PostItem = PostList[number];
// 결과: { title: string; content: string; author: { id: number; name: string; } }
type AuthorType = PostList[number]["author"];
// 배열 요소 타입에서 한 번 더 꺼낼 수도 있다
[number]가 가능한 이유는 배열의 인덱스가 number 타입이기 때문이다.
동일한 타입의 요소로 구성된 배열에서는 요소 하나의 타입이 그대로 반환된다.
유니온이 생기는 건 튜플에서 [number]를 쓸 때인데, 이는 아래에서 따로 다룬다.
튜플에서 사용할 때
튜플에서는 숫자 리터럴 인덱스로 각 위치의 타입을 정확하게 꺼낼 수 있다.
type Tup = [number, string, boolean];
type Tup0 = Tup[0]; // number
type Tup1 = Tup[1]; // string
type Tup2 = Tup[2]; // boolean
type TupNum = Tup[number]; // number | string | boolean
마지막 Tup[number]는 배열과 동일한 원리로, 튜플 내 모든 요소 타입의 유니온을 반환한다.
Keyof 연산자
keyof는 객체 타입에 적용하는 연산자로, 해당 타입의 모든 프로퍼티 키를 유니온 타입으로 만들어준다.
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person;
// 결과: "name" | "age"
이것이 실제로 유용한 이유는 다음 예시에서 잘 드러난다.
function getPropertyKey(person: Person, key: keyof Person) {
return person[key];
}
key 파라미터를 단순히 string으로 타입을 지정했다면, Person에 존재하지 않는 키도 넘길 수 있어서 런타임 오류가 발생할 수 있다.
keyof Person을 사용하면 "name" 또는 "age"만 허용되므로 타입 수준에서 이를 막을 수 있다.
또한 Person 인터페이스에 프로퍼티가 추가되거나 이름이 바뀌어도 keyof가 자동으로 반영하기 때문에 코드를 따로 수정하지 않아도 된다.
typeof와 함께 쓰기
keyof는 타입에만 사용할 수 있다. 값(변수)에 직접 사용할 수 없기 때문에, 변수로부터 타입을 추출하는 typeof와 함께 쓰는 패턴이 자주 등장한다.
const person = {
name: "ydu",
age: 28,
};
type PersonType = typeof person;
// 결과: { name: string; age: number; }
type PersonKeys = keyof typeof person;
// 결과: "name" | "age"
맵드 타입
맵드타입은 기존 객체 타입의 프로퍼티를 순회하면서 새로운 타입을 만들어내는 문법이다.
인덱스 시그니처와 유사하게 생겼지만, 순회 대상이 고정된 키 집합이라는 점이 다르다.
type PartialUser = {
[key in keyof User]?: User[key];
};
[key in keyof User]는 User의 모든 키를 하나씩 순회한다는 의미고, User[key]는 해당 키에 대응하는 원래 타입을 그대로 가져온다는 의미이다. ?를 붙이면 모든 프로퍼티가 선택적이 된다.
같은 원리로 다양한 변형을 만들 수 있다.
// 모든 프로퍼티를 boolean으로 바꾸기
type BooleanUser = {
[key in keyof User]: boolean;
};
// 모든 프로퍼티를 읽기 전용으로 만들기
type ReadonlyUser = {
readonly [key in keyof User]: User[key];
};
선택적 프로퍼티 제거하기(-?)
반대로 모든 선택적 프로퍼티를 필수로 바꾸고 싶을 때는 -? 문법을 사용한다.
type RequiredUser = {
[key in keyof User]-?: User[key];
};
-?는 ?를 제거한다는 의미다. 타입스크립트에는 -readonly도 존재해서 readonly 속성도 동일한 방식으로 제거할 수 있다.
템플릿 리터럴 타입
자바스크립트의 템플릿 리터럴 `${}` 문법을 타입 수준에서도 사용할 수 있다.
type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColoredAnimal = `${Color}-${Animal}`;
// 결과: "red-dog" | "red-cat" | "red-chicken"
// | "black-dog" | "black-cat" | "black-chicken"
// | "green-dog" | "green-cat" | "green-chicken"
유니온 타입을 템플릿 리터럴에 넣으면 타입스크립트가 가능한 모든 조합을 자동으로 만들어준다.
직접 9가지를 나열하는 대신 두 유니온 타입의 조합으로 표현할 수 있다.
정리
인덱스트 액세스, keyof, 맵드 타입, 템플릿 리터럴 타입 4가지 기능들은 각각 독립적으로 보이지만, 실제로는 서로 깊이 연결되어 있다.
맵드 타입은 keyof가 없으면 성립하지 않고, keyof의 결과를 인덱스드 액세스 타입으로 활용하는 패턴은 코드 전반에 걸쳐 반복적으로 등장한다.
이 섹션의 핵심 타입도 연산의 대상이 될 수 있다는 관점의 전환이다. 값을 변환하는 것처럼 타입도 변환할 수 있고, 그 변환 과정을 타입 시스템 안에서 표현할 수 있다. 이 개념은 다음 섹션인 조건부 타입에서 더욱 강력하게 확장된다.
'프론트엔드' 카테고리의 다른 글
| 타입스크립트의 유틸리티 타입 (0) | 2026.03.08 |
|---|---|
| 타입스크립트의 조건부 타입 (0) | 2026.03.07 |
| 타입 시스템을 구조화하는 세 가지 도구(인터페이스, 클래스, 제네릭) (1) | 2026.02.26 |
| TypeScript 함수와 타입 (0) | 2026.02.22 |
| TypeScript 타입 시스템을 계층으로 이해하기 (0) | 2026.02.20 |