유틸리티 타입

유틸리티 타입 (Utility Types)

TypeScript는 흔히 반복되는 타입 변환을 쉽게 처리하도록 몇 가지 유틸리티 타입을 제공해요. AwaitedPartial, Record처럼 이미 전역에 포함되어 있어서 import 없이 바로 쓸 수 있죠. 이 글에서는 각 유틸리티 타입이 어떤 변환을 수행하는지, 그리고 언제 쓰면 좋은지 예시와 함께 살펴볼게요.

출처: TypeScript 핸드북

Awaited<Type>

출시: 4.5

이 타입은 async 함수에서의 awaitPromise.then() 메서드 같은 동작, 정확히는 Promise를 재귀적으로 풀어내는 방식을 모델링하기 위한 타입이에요.

예시

type A = Awaited<Promise<string>>;
//   ^?

type B = Awaited<Promise<Promise<number>>>;
//   ^?

type C = Awaited<boolean | Promise<number>>;
//   ^?

Partial<Type>

출시: 2.1

Type의 모든 프로퍼티를 선택적(optional)으로 만든 타입을 만들어요. 이 유틸리티는 주어진 타입의 모든 부분 집합을 나타내는 타입을 반환해요.

예시

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};

const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

Required<Type>

출시: 2.8

Type의 모든 프로퍼티를 필수(required)로 만든 타입을 구성해요. Partial의 반대 개념이죠.

예시

// @errors: 2741
interface Props {
  a?: number;
  b?: string;
}

const obj: Props = { a: 5 };

const obj2: Required<Props> = { a: 5 };

Readonly<Type>

출시: 2.1

Type의 모든 프로퍼티를 readonly로 만든 타입을 구성해요. 즉 만들어진 타입의 프로퍼티는 재할당할 수 없게 돼요.

예시

// @errors: 2540
interface Todo {
  title: string;
}

const todo: Readonly<Todo> = {
  title: "Delete inactive users",
};

todo.title = "Hello";

이 유틸리티는 런타임에 실패할 할당식, 예를 들어 얼어붙은 객체(frozen object)의 프로퍼티를 재할당하려는 경우를 표현할 때 유용해요.

Object.freeze

function freeze<Type>(obj: Type): Readonly<Type>;

Record<Keys, Type>

출시: 2.1

프로퍼티 키가 Keys이고 프로퍼티 값이 Type인 객체 타입을 구성해요. 이 유틸리티는 한 타입의 프로퍼티들을 다른 타입으로 매핑할 때 쓸 수 있어요.

예시

type CatName = "miffy" | "boris" | "mordred";

interface CatInfo {
  age: number;
  breed: string;
}

const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};

cats.boris;
// ^?

Pick<Type, Keys>

출시: 2.1

Type에서 Keys(문자열 리터럴 또는 문자열 리터럴 합집합)에 해당하는 프로퍼티 집합만 골라 타입을 구성해요.

예시

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

todo;
// ^?

Omit<Type, Keys>

출시: 3.5

Type에서 모든 프로퍼티를 가져온 뒤 Keys(문자열 리터럴 또는 문자열 리터럴 합집합)를 제거해 타입을 구성해요. Pick의 반대 개념이죠.

예시

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}

type TodoPreview = Omit<Todo, "description">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};

todo;
// ^?

type TodoInfo = Omit<Todo, "completed" | "createdAt">;

const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};

todoInfo;
// ^?

Exclude<UnionType, ExcludedMembers>

출시: 2.8

UnionType에서 ExcludedMembers에 할당 가능한 모든 합집합 멤버를 제외해 타입을 구성해요.

예시

type T0 = Exclude<"a" | "b" | "c", "a">;
//    ^?
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
//    ^?
type T2 = Exclude<string | number | (() => void), Function>;
//    ^?

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; x: number }
  | { kind: "triangle"; x: number; y: number };

type T3 = Exclude<Shape, { kind: "circle" }>
//    ^?

Extract<Type, Union>

출시: 2.8

Type에서 Union에 할당 가능한 모든 합집합 멤버를 추출해 타입을 구성해요.

예시

type T0 = Extract<"a" | "b" | "c", "a" | "f">;
//    ^?
type T1 = Extract<string | number | (() => void), Function>;
//    ^?

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; x: number }
  | { kind: "triangle"; x: number; y: number };

type T2 = Extract<Shape, { kind: "circle" }>
//    ^?

NonNullable<Type>

출시: 2.8

Type에서 nullundefined를 제외해 타입을 구성해요.

예시

type T0 = NonNullable<string | number | undefined>;
//    ^?
type T1 = NonNullable<string[] | null | undefined>;
//    ^?

Parameters<Type>

출시: 3.1

함수 타입 Type의 매개변수에 사용된 타입들로 튜플 타입을 구성해요.

오버로드된 함수의 경우 마지막 시그니처의 매개변수가 사용돼요. 조건부 타입 내에서의 추론을 참고하세요.

예시

// @errors: 2344
declare function f1(arg: { a: number; b: string }): void;

type T0 = Parameters<() => string>;
//    ^?
type T1 = Parameters<(s: string) => void>;
//    ^?
type T2 = Parameters<<T>(arg: T) => T>;
//    ^?
type T3 = Parameters<typeof f1>;
//    ^?
type T4 = Parameters<any>;
//    ^?
type T5 = Parameters<never>;
//    ^?
type T6 = Parameters<string>;
//    ^?
type T7 = Parameters<Function>;
//    ^?

ConstructorParameters<Type>

출시: 3.1

생성자 함수 타입의 타입들로 튜플 또는 배열 타입을 구성해요. 모든 매개변수 타입으로 이루어진 튜플 타입을 만들어요 (Type이 함수가 아니면 never 타입을 만들어요).

예시

// @errors: 2344
// @strict: false
type T0 = ConstructorParameters<ErrorConstructor>;
//    ^?
type T1 = ConstructorParameters<FunctionConstructor>;
//    ^?
type T2 = ConstructorParameters<RegExpConstructor>;
//    ^?
class C {
  constructor(a: number, b: string) {}
}
type T3 = ConstructorParameters<typeof C>;
//    ^?
type T4 = ConstructorParameters<any>;
//    ^?

type T5 = ConstructorParameters<Function>;
//    ^?

ReturnType<Type>

출시: 2.8

함수 Type의 반환 타입으로 이루어진 타입을 구성해요.

오버로드된 함수의 경우 마지막 시그니처의 반환 타입이 사용돼요. 조건부 타입 내에서의 추론을 참고하세요.

예시

// @errors: 2344 2344
declare function f1(): { a: number; b: string };

type T0 = ReturnType<() => string>;
//    ^?
type T1 = ReturnType<(s: string) => void>;
//    ^?
type T2 = ReturnType<<T>() => T>;
//    ^?
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
//    ^?
type T4 = ReturnType<typeof f1>;
//    ^?
type T5 = ReturnType<any>;
//    ^?
type T6 = ReturnType<never>;
//    ^?
type T7 = ReturnType<string>;
//    ^?
type T8 = ReturnType<Function>;
//    ^?

InstanceType<Type>

출시: 2.8

Type에 있는 생성자 함수의 인스턴스 타입으로 이루어진 타입을 구성해요.

예시

// @errors: 2344 2344
// @strict: false
class C {
  x = 0;
  y = 0;
}

type T0 = InstanceType<typeof C>;
//    ^?
type T1 = InstanceType<any>;
//    ^?
type T2 = InstanceType<never>;
//    ^?
type T3 = InstanceType<string>;
//    ^?
type T4 = InstanceType<Function>;
//    ^?

NoInfer<Type>

출시: 5.4

포함된 타입으로의 추론을 차단해요. 추론을 차단한다는 점 외에는 NoInfer<Type>Type과 동일해요.

예시

function createStreetLight<C extends string>(
  colors: C[],
  defaultColor?: NoInfer<C>,
) {
  // ...
}

createStreetLight(["red", "yellow", "green"], "red");  // OK
createStreetLight(["red", "yellow", "green"], "blue");  // Error

ThisParameterType<Type>

출시: 3.3

함수 타입의 this 매개변수의 타입을 추출해요. 함수 타입에 this 매개변수가 없으면 unknown을 추출해요.

예시

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

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

OmitThisParameter<Type>

출시: 3.3

Type에서 this 매개변수를 제거해요. Type에 명시적으로 선언된 this 매개변수가 없으면 결과는 그냥 Type이에요. 그렇지 않으면 this 매개변수가 없는 새 함수 타입을 Type으로부터 만들어요. 제네릭은 지워지고 마지막 오버로드 시그니처만 새 함수 타입으로 전파돼요.

예시

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

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

console.log(fiveToHex());

ThisType<Type>

출시: 2.3

이 유틸리티는 변환된 타입을 반환하지 않아요. 대신 문맥적 this 타입을 위한 마커 역할을 해요. 이 유틸리티를 쓰려면 noImplicitThis 플래그를 켜야 해요.

예시

// @noImplicitThis: true
type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // Strongly typed this
      this.y += dy; // Strongly typed this
    },
  },
});

obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

위 예시에서 makeObject 인자의 methods 객체는 ThisType<D & M>을 포함하는 문맥 타입을 가져요. 그래서 methods 객체 안의 메서드들에서 this의 타입은 { x: number, y: number } & { moveBy(dx: number, dy: number): void }가 돼요. methods 프로퍼티의 타입이 동시에 추론 대상이자 메서드 안 this 타입의 원천이 되는 걸 눈여겨보세요.

ThisType<T> 마커 인터페이스는 lib.d.ts에 선언된 그저 빈 인터페이스예요. 객체 리터럴의 문맥 타입에서 인식된다는 점 외에는 다른 빈 인터페이스처럼 동작해요.

내장 문자열 조작 타입(Intrinsic String Manipulation Types)

Uppercase<StringType>

Lowercase<StringType>

Capitalize<StringType>

Uncapitalize<StringType>

템플릿 문자열 리터럴 주변의 문자열 조작을 돕기 위해, TypeScript는 타입 시스템 안에서 문자열 조작에 쓸 수 있는 타입 집합을 포함해요. 자세한 내용은 템플릿 리터럴 타입 문서에서 확인할 수 있어요.

더 알아보기 (Learn more)