타입 조작(type manipulation)
📌 인덱스드 액세스 타입(Indexed Access Types)
인덱스를 이용해 특정 프로퍼티를 추출하는 타입이다.
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
};
}
const post: Post = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "김동현",
},
};
function printAuthorInfo(author: { id: number; name: string }) {
console.log(`${author.id} - ${author.name}`);
}
이렇게 작성하면 Post 타입의 author 프로퍼티가 수정될 때마다
해당 타입도 같이 수정해줘야 한다. 따라서,
function printAuthorInfo(author: Post['author']) {
console.log(`${author.id} - ${author.name}`);
}
이때 중요한 건 [] 안에는 반드시 타입이 들어가야 한다는 점이다.
그래서 다음과 같은 문법은 사용할 수 없다.
const key = 'author';
function printAuthorInfo(author: Post[key]) { // ❌ Error: 타입만 명시할 수 있다.
console.log(`${author.id} - ${author.name}`);
}
또한, 배열에서도 타입을 추출할 수 있다.
type PostList = {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
}[];
function printAuthorInfo(author: PostList[number]['author']) {
console.log(`${author.id} - ${author.name}`);
}
튜플은 크기가 고정되어 있기 때문에, 아래와 같이 사용할 수 있다.
type Tup = [number, string, boolean];
type Tup0 = Tup[0];
// number
type Tup1 = Tup[1];
// string
type Tup2 = Tup[2];
// boolean
type Tup3 = Tup[number];
// number | string | boolean
한 가지 주의할 점은, 튜플 타입에 인덱스드 액세스 타입을 사용할 때
인덱스에 number 타입을 넣으면 마치 튜플을 일반 배열처럼 인식해서
모든 요소의 타입을 합친 유니언 타입을 추출하게 된다는 점이다.
📌 keyof 연산자
객체 타입으로부터 프로퍼티의 모든 key들을 string literal union type으로
추출하는 연산자이다.
keyof연산자는 오직 "타입"에만 적용 가능한 연산자다.
interface Person {
name: string;
age: number;
}
function getProperty(obj: Person, key: 'name' | 'age') {
return obj[key];
}
이런 식으로 타입을 계속 늘릴 수는 없으므로, 이럴 때 사용하는 것이
keyof 연산자다.
function getProperty(obj: Person, key: keyof Person) {
return obj[key];
}
typeof 연산자와 같이 사용하기
function getProperty(obj: Person, key: keyof typeof obj) { return obj[key]; }이런식의 타입 추론도 가능하다.
📌 맵드 타입(Mapped Types)
중복을 피하기 위해 다른 타입을 바탕으로 새로운 타입을 생성할 수 있는 마법 같은 문법
interface User {
id: number;
name: string;
age: number;
}
interface PartialUser {
id?: number;
name?: number;
age?: number;
}
interface BooleanUser {
id?: boolean;
name?: boolean;
age?: boolean;
}
이런 식으로 비슷하지만 조금씩 다른 타입을 필요에 따라 계속 정의해야 할 때가 있다.
이럴 때 필요한 것이 **맵드 타입(Mapped Types)**이며, type 별칭 문법으로만 사용할 수 있다.
type PartialUser = {
[key in 'id' | 'name' | 'age']?: User[key];
};
type BooleanUser = {
[key in 'id' | 'name' | 'age']?: boolean;
};
이렇게 간편하게 줄일 수 있다.
하지만 위에서 본 keyof 연산자를 함께 사용하면 더 안전하고 간편하게 정의할 수 있다.
type PartialUser = {
[key in keyof User]?: User[key];
};
type BooleanUser = {
[key in keyof User]?: boolean;
};
Mapping Modifiers
매핑 중에는 추가로
readonly와?를 사용할 수 있다. 또한-또는+를 접두사로 붙여 이런 수정자를 추가하거나 제거할 수 있다.type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>; // UnlockedAccount는 다음과 같은 타입이 된다. // { // id: string; // name: string; // }그 외에도
as를 사용해 매핑된 키 이름을 다시 한 번 변환할 수 있고,never를 사용해 특정 프로퍼티를 제외하는 것도 가능하다.
📌 템플릿 리터럴 타입(Template Literal Types)
type Color = 'red' | 'blue' | 'green';
type Animal = 'dog' | 'cat' | 'mouse';
type ColorAnimal = `${Color}-${Animal}`;
이런 식으로 문자열 리터럴 유니언 타입들을 조합해서 새로운 문자열 리터럴 타입을 만들 수 있다.

