타입 조작이란 기본 타입, 타입 별칭, 인터페이스로 만든 타입에서 상황에 따라 유동적으로 다른 타입으로 변환하는 타입스크립트의 독특한 기능입니다.
타입 조작에는 다양한 방법으로 타입 조작이 가능합니다.
제네릭은 앞서 알아보았기 때문에 다음 5가지에 대해서 알아보겠습니다.
인덱스트 액세스 타입이란 인덱스를 이용해 객체, 배열, 튜플 등의 프로퍼티 혹은 요소의 타입을 추출하는 타입입니다.
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
};
}
const post: Post = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "정민교",
},
};
Post
라는 게시글을 뜻하는 객체 타입이 있습니다.
게시글의 작성자를 출력해야하는 함수가 있어야 해서 다음과 같이 만들었습니다.
function printAuthorInfo(author: { id: number; name: string }) {
console.log(`${author.id} - ${author.name}`);
}
하지만 위와 같이 만들면 Post
타입의 author
프로퍼티 타입에 변화가 생기면 printAuthorInfo
의 매개변수 타입도 변경해주어야 합니다.
심지어 이런 매개변수에 author
타입을 사용해야 하는 함수가 여러 개라면 모두 찾아 바꿔줘야 합니다.
이럴 때 유용하게 쓸 수 있는 것이 객체 타입의 특정 프로퍼티 타입을 추출하는 인덱스드 액세스 타입입니다.
객체 타입의 인덱스드 액세스 타입은 다음과 같이 사용할 수 있습니다.
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
age: number; // 추가
};
}
function printAuthorInfo(author: Post["author"]) {
console.log(`${author.id} - ${author.name}`);
}
객체에 특정 프로퍼티를 인덱스로 접근하는 것처럼 타입 주석을 작성합니다.
이런식으로 Post
의 author
프로퍼티의 타입을 추출할 수 있습니다.
하지만 객체의 프로퍼티를 인덱스로 접근할 때 특정 변수에 프로퍼티 이름 문자열을 담아서 변수로 프로퍼티에 접근하는 방법
const author = 'author';
Post[author] //❌
이렇게는 인덱스드 액세스 타입을 사용할 수 없습니다.
배열 요소의 타입을 추출하는 인덱스드 액세스 타입에 대해서 알아보겠습니다.
Post
타입을 정의한 인터페이스는 객체 타입을 정의하는데에 특화되어 있으므로 타입 별칭으로 Post
타입의 배열 타입을 정의하겠습니다.
type PostList = {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
}[];
이렇게 PostList
타입을 정의했습니다.
특정 변수에 PostList
배열 요소의 타입을 타입 주석으로 작성하고 싶습니다.
이럴 때 배열의 요소 타입을 추출하는 인덱스드 액세스 타입을 사용할 수 있습니다.
const post: PostList[number] = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "정민교",
age: 29,
},
};
이렇게 하면 post
변수가 PostList
의 요소 타입으로 잘 추론되는 것을 볼 수 있습니다.
이렇게 배열의 요소 타입을 추출하는 인덱스드 액세스 타입은 인덱스에 number
타입을 넣어주면 됩니다.
number
타입이 아닌 number literal
타입을 넣어줘도 동일하게 동작합니다.
const post: PostList[0] = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "정민교",
age: 29,
},
};
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
타입을 인덱스에 작성하면 최적 공통 타입으로 자동 추론하여 number | string | boolean
유니온 타입으로 추론됩니다.
주의할 점은 튜플 타입이기 때문에 존재하지 않은 인덱스를 작성할 경우 오류가 발생합니다.
type Tup = [number, string, boolean];
type Tup0 = Tup[0];
// number
type Tup1 = Tup[1];
// string
type Tup2 = Tup[2];
// boolean
type Tup3 = Tup[3]; // ❌
type Tup4 = Tup[number]
// number | string | boolean
keyof
연산자는 객체 타입의 모든 프로퍼티 키들을 string literal union
타입으로 추출해주는 연산자입니다.
interface Person {
name: string;
age: number;
}
function getPropertyKey(person: Person, key: "name" | "age") {
return person[key];
}
const person: Person = {
name: "정민교",
age: 29,
};
위와 같이 Person
객체 타입을 정의하고 getPropertyKey
함수의 매개변수 key
에 Person
객체 타입의 프로퍼티 이름을 union 타입으로 설정했습니다.
하지만 이렇게 하면 Person
타입에 프로퍼티를 추가할 때 key
매개변수의 타입 주석도 변경해주어야 합니다.
이럴 때 keyof
연산자를 사용하면 좋습니다.
interface Person {
name: string;
age: number;
location: string; // 추가
}
function getPropertyKey(person: Person, key: keyof Person) {
return person[key];
}
const person: Person = {
name: "정민교",
age: 29,
};
keyof
연산자는 타입의 모든 프로퍼티 key를 string literal union
타입으로 추출해주는 연산자입니다.
따라서 매개변수 key
의 타입은 name | age | location
타입이 됩니다.
keyof
연산자는 타입(타입 별칭, 인터페이스 등)에만 적용할 수 있는 연산자입니다.keyof
연산자는 객체 타입의 모든 프로퍼티 키들을 string literal union
타입으로 추출해주는 연산자라고 했습니다.
typeof
연산자는 연산 대상이 되는 값의 타입을 문자열로 반환해주는 연산자입니다.
하지만 타입스크립트에서 typeof
연산자를 타입을 정의할 때 사용하면 특정 변수의 타입을추론하는 기능도 가지고 있습니다.
따라서 다음과 같이 사용할 수도 있습니다.
type Person = typeof person;
// 결과
// {name: string, age: number, location:string}
function getPropertyKey(person: Person, key: keyof typeof person) {
return person[key];
}
const person: Person = {
name: "정민교",
age: 29,
};
맵드 타입은 기존 객체 타입을 기반으로 새로운 객체 타입을 만드는 타입 조작 기능입니다.
interface User {
id: number;
name: string;
age: number;
}
function fetchUser(): User {
return {
id: 1,
name: "정민교",
age: 29,
};
}
function updateUser(user: User) {
// ... 유저 정보 수정 기능
}
updateUser({ // ❌
age: 25
});
user 정보를 불러오는 fetchUser
함수가 있고 유저 정보를 수정하는 updateUser
함수가 있습니다.
user 정보를 수정할 때는 일부 수정되는 프로퍼티만 받아서 수정하면 되는데 매개변수의 타입이 User
타입이기 때문에 일부 프로퍼티만 받을 수가 없습니다.
따라서 새로운 User
타입의 일부를 의미하는 새로운 타입인 PartialUser
타입을 정의해야 합니다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
id?: number;
name?: string;
age?: number;
}
(...)
function updateUser(user: PartialUser) {
// ... 유저 정보 수정 기능
}
updateUser({ // ✅
age: 25
});
이러면 User
객체 타입의 프로퍼티를 추가할 때 PartialUser
객체 타입도 변경해주어야 합니다.
또한 서로 중복된 프로퍼티를 정의하고 있습니다.
이럴 때 맵드 타입을 이용하면 좋습니다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in "id" | "name" | "age"]?: User[key];
};
(...)
맵드 타입은 타입 별칭에만 사용할 수 있습니다.
[key in "id" | "name" | "age"]
는 key가 한 번은 id, 한 번은 name, 한 번은 age가 된다는 뜻입니다.
따라서 다음과 같이 3개의 프로퍼티를 갖는 객체 타입으로 정의됩니다.
대괄호 뒤에 ?키워드가 붙어있기 때문에 모든 프로퍼티가 선택적 프로퍼티가 됩니다.
앞서 언급한 keyof
연산자와 함께 사용하면 더욱 간결하게 표현할 수 있습니다.
keyof
연산자는 객체 타입의 프로퍼티의 키들을 string literal union
타입으로 추출해주는 연산자이기 때문에 다음과 같이 쓸 수 있습니다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in keyof User]?: User[key];
};
(...)
readonly
키워드를 붙여주면 읽기 전용 프로퍼티가 된 타입도 만들 수 있습니다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in keyof User]?: User[key];
};
type ReadonlyUser = {
readonly [key in keyof User]: User[key];
};
(...)
템ㅍ플릿 리터럴 타입은 템플릿 리터럴을 이용해 특정 패턴을 갖는 string
타입을 만드는 기능입니다.
type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColoredAnimal = `red-dog` | 'red-cat' | 'red-chicken' | 'black-dog' ... ;
위와 같은 코드가 있을 때 Color
, Animal
타입을 이용하여 ColoredAnimal
타입과 같은 형태를 만들고 싶습니다.
하지만 이렇게 작성하면 각 타입에 많은 수의 string literal
타입이 추가되면 일일이 작성하기 힘들어집니다.
이럴 때 바로 템플릿 리터럴 타입을 이용하면 좋습니다.
type ColoredAnimal = `${Color}-${Animal}`;