이어서 타입을 조작하는 방법을 정리해보겠습니다.
💡 맵드 타입
맵드 타입은 기존의 객체 타입을 기반으로 새로운 객체 타입을 만들어내는 기능입니다.
// 맵드 타입
interface User {
id: number;
name: string;
age: number;
}
// 1명의 유저 정보를 불러오는 기능
function fetchUser(): User {
return {
id: 1,
name: "shyunu",
age: 27,
};
}
// 1명의 유저 정보를 수정하는 기능
function updateUser(user: User) {
// 수정기능
}
유저 정보를 관리하는 간단한 프로그램의 일부분을 만든다고 가정하겠습니다. 유저 객체 타입, 1명의 유저 정보를 불러오는 기능을 하는 함수 fetchUser, 1명의 유저 정보를 수정하는 기능을 하는 함수 updateUser을 임시로 만들었습니다.
function updateUser(user: User) {
// 수정기능
}
updateUser({ // ❌
age: 25,
});
유저 정보를 수정하기 위해서는 함수를 호출하고 수정하고 싶은 프로퍼티만 전달해주면 됩니다. 하지만 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
});
이러한 상황에서는 다음과 같이 새로운 PartialUser을 만들어 주어야 합니다. PartialUser의 프로퍼티들은 모두 선택적 프로퍼티이고, 함수 updateUser의 매개변수의 타입은 PartialUser이기 때문에 해당 함수를 호출할 때 수정하기를 원하는 프로퍼티만을 전달할 수 있게 되었습니다. 하지만 자세히 보면 User 타입과 PartialUser 타입이 서로 중복된 프로퍼티를 정의하고 있기 때문에 좋지 않은 코드입니다. 이때 사용하면 좋은 것이 맵드 타입입니다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in "id" | "name" | "age"]?: User[key];
};
PartialUSer의 타입을 맵드 타입을 이용하여 수정해보았습니다. [key in "id" | "name" | "age"] 는 이 객체 타입은 key가 id, name, age 중에 하나가 된다는 의미입니다. 따라서 다음과 같이 3개의 프로퍼티를 갖는 객체 타입으로 정의됩니다.
• key = id → id : User[ id ] → id : number
• key = name → id : User[ name ] → id : string
• key = age → id : User[ age ] → id : number
{
id?: number;
name?: string;
age?: number;
}
그리고 대괄호 뒤에 선택적 프로퍼티를 의미하는 물음표(?) 키워드가 붙어있기 떄문에 PartialUser의 모든 프로퍼티가 선택적 프로퍼티가 되어 결론적으로는 이와 같은 타입이 됩니다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in keyof User]?: User[key];
};
또한, 앞서 설명한 대로 keyof 연산자를 사용하여서도 작성할 수 있습니다.
interface User {
id: number;
name: string;
age: number;
}
type ReadonlyUser = {
readonly [key in keyof User]: User[key];
};
function fetchUser(): ReadonlyUser {
return {
id: 1,
name: "shyunu",
age: 27,
};
}
const user = fetchUser();
user.id = 1; // ❌
또한 모든 프로퍼티가 읽기 전용인 프로퍼티가 된 타입 ReadonlyUser을 만들 수 있습니다. 현재 fetchuser함수의 타입은 ReadonlyUser타입입니다. usesr 변수를 통하여 id 프로퍼티의 값을 불러오고자 하였는데 읽기 전용이기 때문에 다음과 같이 오류가 발생하게 됩니다.
💡 템플릿 리터럴 타입
템플릿 리터럴 타입은 타입 조작 기능 중 가장 단순한 기능으로, 템플릿 리터럴을 이용하여 특정 패턴을 갖는 string 타입을 만드는 기능입니다.
// 템플릿 리터럴 타입
type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColoredAnimal = "red-dog" | "red-cat" | "red-chicken" | "black-dog" | "..."; //
Color와 Animal은 각각 3개의 String Literal 타입으로 이루어진 Union 타입입니다. ColoredAnimal은 Color와 Animal을 조합하여 만들 수 있는 모든 경우의 수의 String LIteral 타입으로 이루어진 Union 타입입니다. 이때, Color나 Animal 타입에 String Literal 타입이 추가되어 경우의 수가 더 많아질수록 ColoredAnimal 타입에 추가해야하는 타입이 더 많아지게 됩니다. 이럴 때 사용하면 좋은 것이 템플릿 리터럴 타입입니다.
type ColoredAnimal = `${Color}-${Animal}`;
다음과 같이 코드를 수정하고 ColoredAnimal 타입의 변수를 하나 작성하여 값을 담으려고 하면 자동으로 값들이 추론됩니다.
출처) 한 입 크기로 잘라먹는 타입스크립트(TypeScript)_이정환
'📍 프로그래밍 언어 > TypeScript' 카테고리의 다른 글
[ TypeScript ] 유틸리티 타입 - 맵드 타입 & 조건부 타입 (1) | 2024.12.24 |
---|---|
[ TypeScript ] 조건부 타입 이해하기: 제네릭, 분산적 조건부, infer (0) | 2024.12.23 |
[ TypeScript ] 타입 조작하기 1 - 인덱스드 액세스 타입, keyof & typeof 연산자 (0) | 2024.12.23 |
[ TypeScript ] 제네릭으로 확장하기: 인터페이스, 클래스, 프로미스 (0) | 2024.12.22 |
[ TypeScript ] 제네릭 이해하기: 타입 변수와 메서드 타입 정의(map, forEach) (1) | 2024.12.22 |