1. 타입 조작이란?
- 기본 타입이나 별칭 또는 인터페이스로 만든 원래 존재하던 타입들을 상황에 따라 유동적으로 다른 타입으로 변환하는 기능
- 제네릭 또한 타입 조작 기능에 포함된다.
- 제네릭 이외의 다양한 타입 조작기능이 있다.
2. 인덱스드 엑세스 타입
- 인덱스를 이용해 다른 타입내의 특정 프로퍼티의 타입을 추출하는 타입
- 객체, 배열, 튜플에 사용할 수 있다.
2.1 객체 프로퍼티의 타입 추출하기
만약 게시글을 표현하는 객체 타입과 게시글 변수가 있다고 가정하자.
게시글에서 작성자의 이름과 아이디를 붙여 출력하는 함수도 존재해야한다.
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
age: number; // 추가
};
}
const post: Post = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "이정환",
},
};
function printAuthorInfo(author: { id: number; name: string, age: number }) {
// age 프로퍼티도 추가
console.log(`${author.id} - ${author.name}`);
}
그 후, 새로 age 프로퍼티를 추가한다고 하자.
하지만 이렇게 매개변수의 타입을 정의하면 나중에 Post 타입의 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 프로퍼티 타입을 추출해서 사용할 수 있다.
- Post["author"] 는 Post 타입으로 부터 author 프로퍼티의 타입을 추출한다.
- 대괄호 속에 들어가는 string literal 타입인 "author"를 인덱스 라고 부른다. 따라서 인덱스를 이용해 특정 타입에 접근한다고 해서 인덱스드 엑세스 타입이라고 부른다.
- 이때, 인덱스에는 값이 아닌 타입만 들어갈 수 있다.
🚨 주의할 점
const authorKey = "author";
function printAuthorInfo(author: Post[authorKey]) { // ❌
console.log(`${author.id} - ${author.name}`);
}
function printAuthorInfo(author: Post["what"]) { // ❌
console.log(`${author.id} - ${author.name}`);
}
- 변수에 문자열 값으로 "author"를 저장해서 사용할 수 없다.
- "what" 과 같이 존재하지 않는 프로퍼티 이름을 쓰면 오류가 발생한다.
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
}
function printAuthorInfo(author: Post["author"]['id']) {
// author 매개변수의 타입은 number 타입이 됨
console.log(`${author.id} - ${author.name}`);
}
- 인덱스를 중첩해서 사용할 수 있다.
2.2 배열 요소의 타입 추출하기
type PostList = {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
}[];
const post: PostList[number] = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "이정환",
age: 27,
},
};
const post: PostList[0] = {
title: "게시글 제목",
content: "게시글 본문",
author: {
id: 1,
name: "이정환",
age: 27,
},
};
- 인덱스드 엑세스 타입은 특정 배열의 요소 타입을 추출하는데도 사용할 수 있다.
- 이처럼 PostList 배열 타입에서 하나의 요소의 타입만 뽑아올 수 있다.
- PostList[number]는 PostList 배열 타입으로부터 요소의 타입을 추출하는 인덱스드 엑세스 타입이다. 배열의 요소 타입을 추출할 때 인덱스에 number 타입을 넣어주면 된다.
- 인덱스에 number literal 타입을 숫자와 관계없이 넣어도 number 타입을 넣은 것과 동일하게 동작한다.
2.3 튜플 요소 타입 추출하기
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 타입을 넣으면 마치 튜플을 배열 처럼 인식해 배열 요소의 타입을 추출하게 된다.
3. keyof 연산자
- keyof 연산자는 객체 타입으로부터 프로퍼티의 모든 key들을 strung literal union 타입으로 추출하는 연산자이다.
interface Person {
name: string;
age: number;
location: string; // 추가
}
function getPropertyKey(person: Person, key: "name" | "age" | "location") {
return person[key];
}
const person: Person = {
name: "이정환",
age: 27,
};
- getPropertyKey 함수는 두 개의 매개변수가 존재한다.
- 이때 두번째 매개변수 key에 해당하는 프로퍼티의 값을 첫 번째 매개변수 person에서 꺼내 반환한다.
- key의 타입은 union 타입으로 정의되어 있는데 이때 "location"과 같이 일일히 새로운 프로퍼티를 추가하거나 수정해야하는데 그 수가 많아질수록 불편함이 커진다.
- 이때 keyof 연산자를 사용하면 좋다.
interface Person {
name: string;
age: number;
location: string; // 추가
}
function getPropertyKey(person: Person, key: keyof Person) {
return person[key];
}
const person: Person = {
name: "이정환",
age: 27,
};
- keyof 타입 형태로 사용되며 타입의 모든 프로퍼티 key를 string literal union 타입으로 추출한다.
- keyof Person의 결과값은 "name" | "age" | "location" 이된다.
🚨 주의할 점
(...)
function getPropertyKey(person: Person, key: keyof person) { // ❌
return person[key];
}
const person: Person = {
name: "이정환",
age: 27,
};
- keyof 연산자는 오직 타입에만 적용할 수 있는 연산자라서 값과 함께 사용하면 오류가 발생한다.
3.1 typeof와 keyof 함께 사용하기
type Person = typeof person;
// 결과
// {name: string, age: number, location:string}
(...)
- typeof 연산자는 특정 값의 타입을 문자열로 반환하는 연산자이다.
- 또한 타입을 정의할 때 사용하면 특정 변수의 타입을 추론하는 기능도 가지고 있다.
(...)
function getPropertyKey(person: Person, key: keyof typeof person) {
return person[key];
}
const person: Person = {
name: "이정환",
age: 27,
};
- 이런 특징을 이용해 keyof 연산자와 함께 사용할 수 있다.
4. 맵드 타입
- 기존의 객체 타입을 기반으로 새로운 객체 타입을 만드는 기능
- 예를들어 유저 정보를 관리하는 간단한 프로그램을 만든다고 가정해보자.
interface User {
id: number;
name: string;
age: number;
}
function fetchUser(): User {
return {
id: 1,
name: "이정환",
age: 27,
};
}
function updateUser(user: User) {
// ... 유저 정보 수정 기능
}
updateUser({ // ❌
age: 25
});
- 먼저 유저 객체 타입을 정의한다.
- 다음 유저 정보가 서버에 저장되어 있다고 가정하고, 한명의 유저 정보를 불러오는 기능을 함수로 만든다.
- 한 명의 유저 정보를 수정하는 기능을 함수로 만든다.
- updateUser 함수는 수정된 유저 객체를 받아 유저 정보를 수정한다. 즉, 유저 정보를 수정하려면 함수를 호출하고 여러 개의 정보 중 수정하고 싶은 프로퍼티만 전달한다.
하지만 이때 updateUser함수의 매개변수 타입이 User 타입으로 되어있어 수정하고 싶은 프로퍼티만 골라서 보낼 수 없는 상황이된다.
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];
};
{
id?: number;
name?: string;
age?: number;
}
- [key in "id" | "name" | "age"] 는 이 객체 타입은 key가 각각 id, name, age 가 한번 씩 된다는 것이다.
- key가 "id" 일때 - id: User[id] → id: number
- key가 "name" 일때 - name: User[name] → name: string
- key가 "age" 일때 - age: User[age] → age: number
- 대괄호 뒤에 선택적 프로퍼티를 의미하는 ? 키워드가 붙어있으므로 모든 프로퍼티가 선택적 프로퍼티가 된다.
- 맵드 타입을 이용하면 한 줄의 코드만으로 중복 없이 기존 타입을 변환할 수 있다.
4.1 keyof 연산자 이용하기
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];
};
(...)
- keyof 연산자를 사용하여 더 효율적으로 사용할 수 있다.
- 맵드 타입을 이용해 모든 프로퍼티가 읽기 전용 프로퍼티가 되게 만들 수 있다.
5. 템플릿 리터럴 타입
type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColoredAnimal = `red-dog` | 'red-cat' | 'red-chicken' | 'black-dog' ... ;
type ColoredAnimal = `${Color}-${Animal}`;
- 템플릿 리터럴을 이용해 특정 패턴을 갖는 string 타입을 만드는 기능
- 템플릿 리터럴 타입은 타입 조작 기능 중 가장 단순한 기능이다.
- Color나 Animal 타입에 string literal 타입이 추가되어 수가 많아질 수록 ColoredAnimal 타입에 추가해야하는 타입이 점점 더 많아진다. 이때 템플릿 리터럴 타입을 이용하면 된다.
📌
'Front-end > TypeScript' 카테고리의 다른 글
[TS] 유틸리티 타입 (1) | 2024.02.27 |
---|---|
[TS] 조건부 타입 (0) | 2024.02.22 |
[TS] 제네릭에 대하여 (0) | 2024.02.21 |
[TS] 함수와 타입 (0) | 2024.02.20 |
[TS] 타입을 추론하고... 단언하고... 좁히고... (0) | 2024.02.19 |