[TypeScript] 유틸리티 타입: 복잡한 타입을 간단하게 제공

Awaited<Type>

`Awaited<Type>`은 `Promise`를 재귀적으로 풀어 내부 값의 타입을 반환합니다.

type AsyncString = Awaited<Promise<string>>; // string
type NestedNum = Awaited<Promise<Promise<number>>>; // number
type Mixed = Awaited<string | Promise<boolean>>; // string | boolean

const str: AsyncString = "안녕";
const num: NestedNum = 123;
const mix: Mixed = false;
console.log(str, num, mix); // 출력: 안녕 123 false

Partial<Type>

Partial<Type>은 주어진 타입의 모든 속성을 선택적(optional) 속성으로 바꿉니다.

interface Profile {
  name: string;
  age: number;
}

function patchProfile(profile: Profile, updates: Partial<Profile>): Profile {
  return { ...profile, ...updates };
}

const user: Profile = { name: "민수", age: 28 };
const updated = patchProfile(user, { age: 29 });
console.log(updated); // 출력: { name: "민수", age: 29 }

Required<Type>

Required<Type>은 주어진 타입의 모든 선택적 속성을 필수(required) 속성으로 바꿉니다.

interface Settings {
  mode?: string;
  debug?: boolean;
}

const fullSettings: Required<Settings> = { mode: "dark", debug: true };
// const partial: Required<Settings> = { mode: "light" }; // 오류: debug 누락
console.log(fullSettings); // 출력: { mode: "dark", debug: true }

Readonly<Type>

Readonly<Type>은 모든 속성을 읽기 전용(readonly)으로 만들어, 해당 속성들이 재할당되지 않도록 합니다.

interface Point {
  x: number;
  y: number;
}

const fixedPoint: Readonly<Point> = { x: 10, y: 20 };
// fixedPoint.x = 15; // 오류: 읽기 전용
console.log(fixedPoint); // 출력: { x: 10, y: 20 }

Record<Keys, Type>

Record<Keys, Type>은 지정한 키 집합(Keys)에 대해 모든 값을 동일한 타입으로 갖는 객체 타입을 만듭니다.

type Color = "red" | "blue";
interface ColorData {
  hex: string;
}

const palette: Record<Color, ColorData> = {
  red: { hex: "#FF0000" },
  blue: { hex: "#0000FF" },
};

console.log(palette.red); // 출력: { hex: "#FF0000" }

Pick<Type, Keys> 와 Omit<Type, Keys>

  • Pick<Type, Keys>: 주어진 타입에서 특정 키들만 선택하여 새로운 타입을 만듭니다.
interface Item {
  id: string;
  title: string;
  price: number;
}

type ItemSummary = Pick<Item, "id" | "title">;

const summary: ItemSummary = { id: "i1", title: "책" };
console.log(summary); // 출력: { id: "i1", title: "책" }
  • Omit<Type, Keys>: 주어진 타입에서 특정 키들을 제거한 타입을 만듭니다.
type ItemNoPrice = Omit<Item, "price">;

const noPrice: ItemNoPrice = { id: "i2", title: "펜" };
console.log(noPrice); // 출력: { id: "i2", title: "펜" }

Exclude<UnionType, ExcludedMembers> 와 Extract<Type, Union>

  • Exclude<UnionType, ExcludedMembers>: 유니온 타입에서 지정한 타입들을 제외합니다.
type Letters = "a" | "b" | "c" | "d";
type Excluded = Exclude<Letters, "a" | "d">; // "b" | "c"
  • Extract<Type, Union>: 유니온 타입에서 지정한 타입만 추출합니다.
type Mixed = "a" | "b" | "c";
type Extracted = Extract<Mixed, "a" | "c">; // "a" | "c"

NonNullable<Type>

NonNullable<Type>은 타입에서 null과 undefined를 제거합니다.

type MaybeNumber = string | number | null | undefined;
type DefiniteNumber = NonNullable<MaybeNumber>; // string | number

Parameters<Type> 와 ConstructorParameters<Type>

  • Parameters<Type>: 함수 타입의 매개변수 타입들을 튜플로 추출합니다.
function concat(a: string, b: string): string {
  return a + b;
}

type ConcatParams = Parameters<typeof concat>; // [string, string]
  • ConstructorParameters<Type>: 생성자 함수의 매개변수 타입들을 튜플로 추출합니다.
class Point {
  constructor(public x: number, public y: number) {}
}

type PointParams = ConstructorParameters<typeof Point>; // [number, number]

ReturnType<Type> 와 InstanceType<Type>

  • ReturnType<Type>: 함수 타입의 반환 타입을 추출합니다.
function getUser(): { name: string; id: number } {
  return { name: "Charlie", id: 3 };
}

type UserReturn = ReturnType<typeof getUser>; // { name: string; id: number }
  • InstanceType<Type>: 생성자 함수 타입에서 인스턴스 타입을 추출합니다.
type PointInstance = InstanceType<typeof Point>; // Point

NoInfer<Type>

NoInfer<Type>은 타입 추론을 막아, 명시적으로 전달된 타입을 유지하도록 합니다.

function pick<T>(collection: readonly T[], item: NoInfer<T>): T {
  return collection.find(_item => _item === item) as T;
}

const fruits = ["apple", "banana", "tomato"] as const;
const item = pick(fruits, "apple"); // apple
const item = pick(fruits, "apple2"); // error

ThisParameterType<Type> 와 OmitThisParameter<Type>

  • ThisParameterType<Type>: 함수 타입에서 this 매개변수의 타입을 추출합니다.
  • OmitThisParameter<Type>: 함수 타입에서 this 매개변수를 제거한 새로운 타입을 만듭니다.
function example(this: { value: number }, increment: number) {
  return this.value + increment;
}

type TThis = ThisParameterType<typeof example>; // { value: number }
type WithoutThis = OmitThisParameter<typeof example>; // (increment: number) => number

ThisType<Type>

ThisType<Type>은 객체 리터럴에서 메서드의 this 타입을 지정할 때 사용됩니다.

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>;
};

function createObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  return { ...desc.data, ...desc.methods } as D & M;
}

const obj = createObject({
  data: { x: 10, y: 20 },
  methods: {
    move(dx: number, dy: number) {
      this.x += dx;
      this.y += dy;
    },
  },
});

obj.move(5, 5);
console.log(obj); // { x: 15, y: 25, move: [Function] }

내장 문자열 조작 타입

  • Uppercase<StringType>: 문자열을 모두 대문자로 변환합니다.
  • Lowercase<StringType>: 문자열을 모두 소문자로 변환합니다.
  • Capitalize<StringType>: 첫 글자를 대문자로 변환합니다.
  • Uncapitalize<StringType>: 첫 글자를 소문자로 변환합니다.
type Original = "hello, typescript";
type Shout = Uppercase<Original>;       // "HELLO, TYPESCRIPT"
type Whisper = Lowercase<Original>;       // "hello, typescript"
type Formal = Capitalize<Original>;       // "Hello, typescript"
type Casual = Uncapitalize<"WORLD">;        // "wORLD"

console.log(Shout, Whisper, Formal, Casual);