맵드 타입 기반의 유틸리티 타입 1 - Partial, Required, Readonly

woodstockwoodstock
3 min read

Partial

Partial<T>은 특정 객체 타입의 모든 프로퍼티를 선택적 프로퍼티로 변환한다. 즉, 기존 객체 타입에 정의된 프로퍼티들 중 일부분만 사용할 수 있도록 도와주는 타입이다.

예제

간단한 블로그 플랫폼의 일부를 직접 구현한다고 가정해보자.

// 게시글 타입
interface Post {
  title: string;
  tags: string[];
  content: string;
  thumbnailURL?: string;
}
// 임시 저장된 게시글을 변수로 저장

const draft: Post = { // ❌ tags 프로퍼티가 없음
  title: "제목은 나중에 짓자...",
  content: "초안...",
};

위와 같이 게시글의 일부 정보가 아직 설정되어 있지 않은 임시 저장 게시글의 경우에도 변수에 저장할 수 있어야 하는데 해당 변수를 Post 타입으로 정의하면 오류가 발생하게 된다.

그렇다고 임시 저장 게시글 기능을 위해 Post 타입의 모든 프로퍼티를 선택적 프로퍼티로 설정하는 것도 곤란하다. 진짜 작성이 완료되어 화면에 렌더링 될 게시글들은 이 모든 프로퍼티를 다 가지고 있어야 하기 때문이다.

Partial 타입으로 문제 해결하기

interface Post {
  title: string;
  tags: string[];
  content: string;
  thumbnailURL?: string;
}

const draft: Partial<Post> = {
  title: "제목 나중에 짓자",
  content: "초안...",
};


Partail 구현하기

일단 하나의 타입 변수 T를 사용하는 제네릭 타입인 것 만은 확실하다.

type Partial<T> = any;

다음으로는 T에 할당된 객체 타입의 모든 프로퍼티를 선택적 프로퍼티로 바꿔줘야 한다.

기존 객체 타입을 다른 타입으로 변환하는 맵드 타입을 이용해 다음과 같이 수정한다.

type Partial<T> = {
  [key in keyof T]?: T[key];
};



Required

Required<T>는 특정 객체 타입의 모든 프로퍼티를 필수 프로퍼티로 변환한다.

예제

이번에는 썸네일이 반드시 있어야 하는 게시글이 하나 필요하다고 가정해보자.

interface Post {
  title: string;
  tags: string[];
  content: string;
  thumbnailURL?: string;
}

(...)

// 반드시 썸네일 프로퍼티가 존재해야 하는 게시글
const withThumbnailPost: Post = {
  title: "유틸리티 타입",
  tags: ["ts"],
  content: "",
  thumbnailURL: "https://...",
};

withThumbnailPost는 모종의 이유(마케팅 등)로 반드시 썸네일이 포함된 게시글이어야 한다.

그런데 Post 타입의 thumbnailURL 프로퍼티가 현재 선택적 프로퍼티로 설정되어 있기 때문에 다음과 같이 실수로 주석 처리하거나 삭제한다고 해도 타입 오류가 발생하지는 않는다.

Required 타입으로 문제 해결하기

const withThumbnailPost: Required<Post> = {
  title: "유틸리티 타입",
  tags: ["ts"],
  content: "",
  thumbnailURL: "https://...",
};


Required 타입 구현하기

일단 기존의 모든 프로퍼티를 포함하는 제네릭 맵드 타입으로 만들어준다.

type Required<T> = {
  [key in keyof T]: T[key];
};

그리고 이제 모든 프로퍼티가 필수 프로퍼티가 되도록 만들어야 한다.

모든 프로퍼티를 필수 프로퍼티로 만든다는 말은 반대로 바꿔보면 모든 프로퍼티에서 ‘선택적’ 이라는 기능을 제거하는 것과 같다.

따라서 다음과 같이 -?를 프로퍼티 이름 뒤에 붙여주면 된다.

type Required<T> = {
  [key in keyof T]-?: T[key];
};

-??가 붙어있는 선택적 프로퍼티가 있으면 ?를 제거하라는 의미이다.



Readonly

Readonly<T>는 특정 객체 타입의 모든 프로퍼티를 읽기 전용 프로퍼티로 변환한다.

예제

이번에는 앞서 만들던 예제에 이어서 절대 내부를 수정할 수 없는 보호된 게시글이 하나 필요하다고 가정해보자.

interface Post {
  title: string;
  tags: string[];
  content: string;
  thumbnailURL?: string;
}

(...)

const readonlyPost: Post = {
  title: "보호된 게시글입니다.",
  tags: [],
  content: "",
};

readonlyPost.content = '해킹당함';

변수 readonlyPost는 보호받아야 하는 게시글로 절대 객체 내부의 값을 수정하지 못하게 막아야 한다고 가정했다.

그러나 Post 타입의 모든 프로퍼티가 다 readonly 설정이 안되어 있기 때문에 지금은 수정을 방지하지 못한다.

Readonly 타입으로 문제 해결하기

const readonlyPost: Readonly<Post> = {
  title: "보호된 게시글입니다.",
  tags: [],
  content: "",
};

readonlyPost.content = '해킹당함'; // ❌


Readonly 타입 구현하기

type Readonly<T> = {
  readonly [key in keyof T]: T[key];
};

0
Subscribe to my newsletter

Read articles from woodstock directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

woodstock
woodstock

안녕하세요! 프론트엔드 개발자 woodstock입니다. 저는 매일 조금씩 발전하고자 하는 마음으로 개발공부를 시작했고, 이 블로그는 그 과정에서 배우고 성장하는 이야기를 담고 있습니다. 여러분의 피드백과 조언은 언제나 환영합니다! 함께 배우고 성장하는 과정을 즐길 수 있기를 기대합니다.