프론트엔드/TypeScript

[TypeScript] 유틸리티 타입 구현하기 (Utility Types)

TypeScript이 제공하는 다양한 유틸리티 타입에 대해 정리하고자 합니다.

내부 로직 크게 어렵지 않기 때문에 자주 쓰이는 유틸들은 직접 구현해보는 것을 추천합니다.

(면접 때 물어봐서 당황했던 기억이 나네요 😅)

 

 


💡 용어 정리

  • T : 타입
  • U : 또 다른 타입
  • K : 속성(key)

 

 


Partial<T>

T의 모든 프로퍼티를 선택적 프로퍼티로 변경한 새로운 타입을 반환합니다.

 

구현

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

keyof T는 T의 모든 키 값을 가지는 유니온 타입을 의미합니다.   

인덱스 시그니처 문법이 아직 익숙지 않으면 이 글을 참고하면 좋습니다.

 

예시

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

interface PartialUser {
  name?: string;
  age?: number;
}

 

 


Required<T>

Partial과 반대로 T의 모든 프로퍼티를 필수 프로퍼티로 변경한 새로운 타입을 반환합니다.

 

구현

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

마이너스 기호는 2.8 버전에 추가된 일종의 remove modifiers입니다.

즉 옵셔널 프로퍼티(? 기호)를 제거하는 역할을 합니다.

 

예시

interface User {
  name?: string;
  age?: number;
}

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

 

 


Readonly<T>

T의 모든 프로퍼티를 읽기 전용 프로퍼티로 변경한 새로운 타입을 반환합니다.

 

구현

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

 

예시

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

interface ReadonlyUser {
  readonly name: string;
  readonly age: number;
}

 

 


Record<K, T>

K를 속성으로, T를 그 속성 값의 타입으로 갖는 새로운 타입을 반환합니다.

 

구현

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

제네릭의 extends는 부분집합으로 바라보면 해석이 편리합니다.

K는 keyof any의 부분집합 형태입니다.

 

예시

type User = 'kim' | 'so'

// Record<User, number>
interface RecordUser {
  kim: number;
  so: number;
}

 

 


Pick<T, K>

T에서 K의 집합으로 이루어진 새로운 타입을 반환합니다.

 

구현

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

K extends keyof T는 K가 T의 키 값으로 이루어진 유니온 타입으로부터 확장됨을 의미합니다.

 

예시

type Keys = 'name' | 'email';

interface User {
  name: string;
  age: number;
  email: string;
}

// Pick<User, Keys>
interface PickUser {
  name: string;
  email: string;
}

 

 


Omit<T, K>

Pick과 반대로 T에서 K의 집합을 제외한 새로운 타입을 반환합니다.

 

구현

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

 

예시

type Keys = 'name' | 'email';

interface User {
  name: string;
  age: number;
  email: string;
}

// Omit<User, Keys>
interface OmitUser {
  age: number;
}

 

 


Exclude<T, U>

T에서 U를 제외한 새로운 타입을 반환합니다.

 

구현

type Exclude<T, U> = T extends U ? never : T;

 

예시

type T = string | number | boolean;
type U = number | boolean;

type V = Exclude<T, U>; // string

 

 


Extract<T, U>

Exclude와 반대로 T에서 U와 겹치는 타입을 반환합니다. 

 

구현

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

 

예시

type T = string | number | boolean;
type U = number | boolean;

type V = Extract<T, U>; // number | boolean

 

 


NonNullable<T>

T에서 null과 undefined를 제외한 새로운 타입을 반환합니다.

 

구현

type NonNullable<T> = T extends null | undefined ? never : T;

 

예시

type T = string | null | undefined;

type U = NonNullable<T>; // string

 

 


Parameters<T>

함수 타입 T의 매개변수 타입을 튜플 형태의 타입으로 반환합니다.

 

구현

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

 

예시

function func(a: string | number, b: boolean) {
  return [a, b];
}

type T = Parameters<typeof func>; // [a: string | number, b: boolean]

 

 


ConstructorParameters<T>

클래스 타입 T의 매개변수 타입을 튜플 형태의 타입으로 반환합니다.

 

구현

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

 

예시

class User {
  constructor(
    public name: string,
    private age: number,
  ) {}
}

type T = ConstructorParameters<typeof User>; // [name: string, age: number]

 

 


ReturnType<T>

함수 타입 T의 반환 타입을 새로운 타입으로 반환합니다.

 

구현

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

 

예시

function func(a: string | number, b: boolean) {
  return [a, b];
}

type T = ReturnType<typeof func>; // (string | number | boolean)[]

 

 


InstanceType<T>

클래스 타입 T의 인스턴스 타입을 반환합니다.

 

구현

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

 

예시

class User {
  constructor(
    public name: string,
    private age: number,
  ) {}
}

type T = InstanceType<typeof User>; // User

 

 


ThisParameterType<T>

함수 타입 T의 명시적 this의 매개변수 타입을 새로운 타입으로 반환합니다. 

 

예시

function toHex(this: Number) {
  return this.toString(16);
}

function numberToString(n: ThisParameterType<typeof toHex>) { // Number
  return toHex.apply(n);
}

 

 


OmitThisParameter<T>

함수 타입 T의 명시적 this의 매개변수 타입을 제외한 새로운 타입을 반환합니다. 

 

예시

function toHex(this: Number) {
  return this.toString(16);
}

const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5); // () => string

 

 


ThisType<T>

T의 this 컨텍스트를 명시적으로 설정합니다. 다만 별도의 타입을 반환하지 않습니다.

 

예시

interface User {
  name: string;
  getName: () => string;
}

function makeNeo(methods: ThisType<User>) {
  return { name: 'Neo', ...methods } as User;
}
const neo = makeNeo({
  getName() {
    return this.name;
  },
});

neo.getName(); // Neo