유틸리티 타입 (1)

About TS, TS기본, 유틸리티_타입
2023/10/29

유틸리티 타입?

"TypeScript는 공통 타입 변환을 용이하게 하기 위해 몇 가지 유틸리티 타입을 제공합니다."

- 타입스크립트 핸드북

Partial

Partial<T>는 타입 T모든 프로퍼티를 선택적으로 만드는 타입을 구성합니다.

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

Partial<T>는 특정 타입 T의 모든 프로퍼티에 ?를 붙여 선택적 프로퍼티로 만듭니다.

interface IData {
    title: string;
    timestamp: number;
    description: string;
    tags: tag[]
}
function updateData(currentData: IData, newData: ?) {
    update({
    ...currentData,
    ...newData,
    timestamp: getTimestamp(),
    });
}

일반적으로 업데이트 함수는 기존 데이터의 특정 부분을 변경합니다. updateData 함수는 newData를 인자로 받아 해당 데이터를 타임스탬프와 함께 업데이트합니다.

스프레드 연산자를 사용한 것으로 보아 newData가 어떤 프로퍼티를 가지는 지 확실하지 않습니다. title을 업데이트 할 수도, description이나 tags를, 또는 셋 모두 업데이트 할 수도 있습니다.

따라서 이 경우, IData의 모든 프로퍼티를 옵셔널로 만드는 Partial 유틸리티 타입이 적합합니다.

Partial<IData>
    {
    title?: string;
    timestamp?: number;
    description?: string;
    tags?: tag[]
    }

Required

Partial<T>와 반대로 모든 프로퍼티를 필수로 설정하는 타입을 만드는 Required<T> 유틸리티 타입도 있습니다.

interface IData {
  title?: string;
  description?: string;
}
const a: IData = { title: "some title" }; // description이 없어도 OK.
const b: Required<IData> = { title: "some title" }; // 오류: 프로퍼티 description이 없습니다.

Omit

Omit<T, K>은 타입 T의 프로퍼티 중 K를 제거한 타입을 구성합니다.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

T의 프로퍼티를 인자로 받으며(K extends keyof T), T 프로퍼티 중 K를 제외한(Pick<T, Exclude<keyof T, K>>) 새로운 타입을 구성합니다.

퀴즈의 updateData 함수는 newData 프로퍼티가 명확하지 않아도 범용적으로 사용할 수 있는 함수로 특정 프로퍼티를 제외하는 Omit<T, K>는 적합하지 않습니다. 추상화 레벨이 한 단계 높아진다면 사용할 수는 있습니다. 아래와 같이요.

function updateTitle(
  currentData: IData,
  newData: Omit<IData, "description" | "tags">
) {
  update({
    ...currentData,
    ...newData,
  });
}

하지만 위와 같이 프로퍼티를 특정해 어떤 동작을 수행하는 경우, IData가 확장될 경우 의도하지 않은 속성이 같이 업데이트 될 위험이 있습니다. newData.title을 특정해 title을 업데이트 할 수도 있겠지만 위 경우 대상 프로퍼티를 정의해 주는 것이 논리적으로 적절합니다.

이럴 경우에 Pick<T, K>을 사용합니다.

Pick

Pick<T, K>은 타입 T에서 특정 프로퍼티를 선택해 새로운 타입을 구성합니다.

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

두 번째 인자 KT 타입의 프로퍼티를 받으며, K를 프로퍼티로 가지는 새로운 타입을 구성합니다. 위의 updateTitle()에 아래와 같이 적용할 수 있습니다.

function updateTitle(currentData: IData, newData: Pick<IData, "title">) {
  update({
    ...currentData,
    ...newData,
  });
}

Record

Record<K, T>첫 번째 인자 타입 K를 프로퍼티로, 해당 프로퍼티의 타입으로 T를 가지는 새로운 객체 타입을 구성합니다.

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

아래와 같이 사용할 수 있습니다.

type dataState = "pending" | "fulfilled" | "error"
const obj: Record<dataState, IData[]> = {
    pending: IData 타입의 객체 배열,
    fulfilled: IData 타입의 객체 배열,
    error: IData 타입의 객체 배열,
}

Partial과의 콤비네이션도 가넝.

type dataState = "pending" | "fulfilled" | "error"
const obj: Partial<Record<dataState, IData[]>> = {
    pending: IData 타입의 객체 배열
} // fulfilled, error 프로퍼티가 없어도 OK.