타입 조작(type manipulation)이란 기본 타입이나 별칭 또는 인터페이스로 만든 원래 존재하던 타입들을 상황에 따라 유동적으로 다른 타입으로 변환하는 타입스크립트의 기능입니다.
제네릭도 함수나 인터페이스, 타입 별칭, 클래스 등에 적용해서 상황에 따라 달라지는 가변적인 타입을 정의할 수 있기 때문에 타입을 조작하는 기능에 포함됩니다. 타입스크립트에서는 제네릭 이외에도 다양한 타입 조작 기능을 제공합니다.
인덱스 접근 타입(Indexed Access Types)은 기존 타입의 특정 프로퍼티의 타입을 추출하는 데 사용됩니다.
// object indexed access type
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
location: string;
};
}
const authorKey = "author";
function printAuthorInfo(author: Post["author"]) { // ✅ OK
// function printAuthorInfo(author: Post["author"]["name"]) { // ✅ OK
// function printAuthorInfo(author: Post[authorKey]) { // ❌ No, 타입을 전달해야 합니다.
// function printAuthorInfo(author: Post["what"]) { // ❌ No, 존재하지 않는 프로퍼티입니다.
console.log(`${author.name}-${author.id}`);
}
const post: Post = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "이정환",
},
};
printAuthorInfo(post.author);
// array indexed access type
type PostList = {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
location: string;
};
}[];
function printAuthorInfo(author: PostList[number]["author"]) {
console.log(`${author.name}-${author.id}`);
}
const post: PostList[number] = {
// const post: PostList[0] = { // ✅ OK
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "이정환",
},
};
printAuthorInfo(postList.author);
// tuple indexed access type
type Tup = [number, string, boolean];
type Tup0 = Tup[0]; // Tup0 = number
type Tup1 = Tup[1]; // Tup1 = string
type Tup2 = Tup[2]; // Tup2 = boolean
type Tup3 = Tup[3]; // ❌ No
type TupNum = Tup[number]; // TupNum = number | string | boolean
keyof
연산자는 타입스크립트에서 제공하는 특별한 연산자로, 객체의 모든 키의 집합을 문자열 리터럴 유니언 형태로 반환합니다. 이를 통해 특정 객체의 키에만 접근하는 것을 보장할 수 있습니다.
interface Person {
name: string;
age: number;
location: string; // 추가
}
// keyof 연산자 뒤에는 타입을 전달해야 합니다.
function getPropertyKey(person: Person, key: keyof Person) { // key: "name" | "age"
return person[key];
}
const person: Person = {
name: "John Doe",
age: 27,
location: "Seoul"
};
console.log(getPropertyKey(person, "name")); // "John Doe"
console.log(getPropertyKey(person, "age")); // 27
console.log(getPropertyKey(person, "location")); // "Seoul"
typeof
연산자는 변수의 타입을 가져오는 데 사용되며, 이를 keyof
와 결합하면 특정 객체의 키를 동적으로 추출할 수 있습니다.
const person = {
name: "John Doe",
age: 27,
location: "Seoul"
};
function getPropertyKey(person: typeof person, key: keyof typeof person) {
return person[key];
}
console.log(getPropertyKey(person, "name")); // "John Doe"
console.log(getPropertyKey(person, "age")); // 27
console.log(getPropertyKey(person, "location")); // "Seoul"
Mapped type
은 기존의 타입을 변환하여 새로운 타입을 생성하는 기능을 제공합니다. 이를 통해 기존 타입의 모든 속성을 새로운 형태로 mapping
할 수 있습니다. 주로 기존 타입의 모든 필드를 선택적으로 만들거나, 읽기 전용으로 만들거나, 다른 타입으로 변환하는 데 사용됩니다.
interface User {
id: number;
name: string;
age: number;
}
// 모든 속성을 선택적으로 만드는 mapped type
type PartialUser = {
[key in keyof User]?: User[key];
};
// 모든 속성을 읽기 전용으로 만드는 mapped type
type ReadonlyUser = {
readonly [key in keyof User]: User[key];
};
// 사용자 정보를 불러오는 함수: 반환된 사용자 정보는 변경할 수 없습니다.
function fetchUser(): ReadonlyUser {
return {
id: 1,
name: "이정환",
age: 27,
};
}
// 사용자 정보를 업데이트하는 함수: 일부 정보만 업데이트해도 됩니다.
function updateUser(user: PartialUser) {
// ...
}
// age만 업데이트
updateUser({
age: 25,
});
Template literal types는 TypeScript 4.1 버전에서 도입된 새로운 기능으로, 문자열 리터럴 타입을 템플릿 리터럴 문법으로 결합하여 새로운 문자열 리터럴 타입을 생성할 수 있게 해줍니다.
type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
// 색상과 동물을 결합한 타입
type ColoredAnimal = `${Color}-${Animal}`;
Reference