Language/Javascript

Typescript - 유틸리티 타입(Utility Type)

둉이 2022. 9. 25. 22:42

 

본격적인 업무에 들어가기 앞서, 타입스크립트에서 유용하게 사용할 수 있는 유틸리티 타입 종류에 대해 정리했다.

 

유틸리티 타입 종류는 아래 타입스크립트 공식 문서를 참고했다.

 

Documentation - Utility Types

Types which are globally included in TypeScript

www.typescriptlang.org

 

 


 

일반 유틸리티 타입

Awaited<T>

Promise<?> 형태의 T 타입을 전달받아, 해당 Promise가 반환하는 리턴값의 타입을 반환한다.

 

async ~ await의 await 키워드와 유사한 기능을 담당한다.

type A = Awaited<Promise<string>>;  // type A = string
 
type B = Awaited<Promise<Promise<number>>>;  // type B = number
 
type C = Awaited<boolean | Promise<number>>;  // type C = number | boolean

 

 

Partial<T>

T 타입의 일부 프로퍼티만 가질 수 있는 타입(subset)을 반환한다.

 

모든 프로퍼티는 optional로 취급되며, 모든 프로퍼티를 갖지 않는 빈 객체{ }도 허용된다.

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

let pointPart: Partial<Point> = {};  // x와 y는 선택적 프로퍼티
pointPart.x = 10;

 

 

Required<T>

T 타입의 모든 프로퍼티를 필수로 갖는 타입을 반환한다.

 

기존 타입 내의 모든 optional 프로퍼티는 필수 속성으로 변경되며, 하나의 프로퍼티라도 누락되면 에러가 발생한다.

interface Car {
  make: string;
  model: string;
  mileage?: number;
}

let myCar: Required<Car> = {
  make: 'Ford',
  model: 'Focus',
  mileage: 12000,  // mileage는 optional이 아닌 필수 프로퍼티
};

 

 

Readonly<T>

T 타입의 모든 프로퍼티를 readonly(읽기 전용)로 변환한 타입을 반환한다.

 

readonly 타입을 가진 값은 수정이 불가능하므로, 해당 타입의 값을 변경하게 되면 에러가 발생한다.

interface Todo {
  title: string;
}

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

todo.title = 'Hello';  // readonly 타입이므로 Error!

 

 

Record<K, T>

K를 key로, T를 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" },
};

 

K 타입으로 string, number, symbol을 사용할 경우에는 다음과 같이 선언해도 Record<K, T>로 선언한 타입과 동일하게 사용할 수 있다.

const nameAgeMap: Record<string, number> = {
  'Alice': 21,
  'Bob': 25
};

// 위의 예제와 동일한 결과 타입을 가짐
const nameAgeMap: { [key: string]: number } = {
  'Alice': 21,
  'Bob': 25
};

 

 

Pick<T, U>

T 타입에서 2번째 인자로 전달한 타입에 지정된 키만 프로퍼티로 갖는 새로운 타입을 반환한다.

 

2번째 인자로 지정하는 타입은 단일 타입도 가능하고 아래 예시처럼 유니온 타입으로도 지정할 수 있다.

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {  // title, completed 프로퍼티만 갖는 TodoPreview 타입
  title: "Clean room",
  completed: false,
};

 

 

Omit<T, U>

T 타입에서 2번째 인자로 전달한 유니온에 지정된 키만 프로퍼티로 갖지 않는 새로운 타입을 반환한다.

 

Pick<T, U> 유틸 타입과 정확히 반대의 기능을 수행한다.

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
 
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {  // description 프로퍼티가 빠진 타입 TodoPreview
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};
 
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
 
const todoInfo: TodoInfo = {  // completed, createdAt이 빠진 타입 TodoInfo
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};

 

 

Exclude<T,  U>

T 타입에서 U 타입과 공통되는 프로퍼티를 제외한 나머지 프로퍼티를 타입으로 추출하여 반환한다.

 

벤 다이어그램의 T - U와 동일한 기능을 수행한다.

type T0 = Exclude<"a" | "b" | "c", "a">;  // type T0 = "b" | "c"

type T1 = Exclude<"a" | "b" | "c", "a" | "b">;  // type T1 = "c"

type T2 = Exclude<string | number | (() => void), Function>;  // type T2 = string | number

 

 

Extract<T,  U>

T 타입에서 U 타입과 공통되는 모든 프로퍼티를 타입으로 추출하여 반환한다.

 

벤 다이어그램의 T ∩ U(단, T > U)와 유사한 기능을 수행한다.

type T0 = Extract<"a" | "b" | "c", "a" | "f">;  // type T0 = "a"

type T1 = Extract<string | number | (() => void), Function>;  // type T1 = () => void

 

 

NonNullable<Type>

T 타입에서 Nullable한 타입인 null, undefined를 제외한 타입을 반환한다.

type T0 = NonNullable<string | number | undefined>;  // type T0 = string | number

type T1 = NonNullable<string[] | null | undefined>;  // type T1 = string[]

 

 

Parameters<T>

함수 타입 T의 매개변수 타입을 튜플(tuple) 형태로 반환한다.

 

튜플 내의 타입은 배열처럼 인덱스로 접근 가능하며, 함수 타입이 아닌 타입을 T로 전달하면 에러가 발생한다.

declare function f1(arg1: { a: number; b: string }, arg2?: string): void;
 
type T0 = Parameters<() => string>;  // type T0 = []
type T1 = Parameters<(s: string) => void>;  // type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;  // type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;  // type T3 = [arg1: { a: number; b: string; }, arg2?: string | undefined]
type T4 = Parameters<any>;  // type T4 = unknown[]
type T5 = Parameters<never>;  // type T5 = never
type T6 = Parameters<string>;  // Error, type T6 = never
type T7 = Parameters<Function>;  // Error, type T7 = never

 

 

ConstructorParameters<T>

생성자 함수 타입 T의 매개변수 타입을 튜플(tuple) 형태로 반환한다.

 

Parameters<T>와 유사하지만, 일반 함수나 생성자 함수 타입이 아닌 타입을 T로 전달하면 에러가 발생한다.

type T0 = ConstructorParameters<ErrorConstructor>;  // type T0 = [message?: string]
type T1 = ConstructorParameters<FunctionConstructor>;  // type T1 = string[]
type T2 = ConstructorParameters<RegExpConstructor>;  // type T2 = [pattern: string | RegExp, flags?: string]
type T3 = ConstructorParameters<any>;  // type T3 = unknown[]
type T4 = ConstructorParameters<Function>;  // Error, type T4 = never

 

 

ReturnType<T>

함수 타입 T의 리턴 타입 유형을 반환한다.

declare function f1(): { a: number; b: string };
 
type T0 = ReturnType<() => string>;  // type T0 = string
type T1 = ReturnType<(s: string) => void>;  // type T1 = void
type T2 = ReturnType<<T>() => T>;  // type T2 = unknown
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;  // type T3 = number[]
type T4 = ReturnType<typeof f1>;  // type T4 = { a: number; b: string; }
type T5 = ReturnType<any>;  // type T5 = any
type T6 = ReturnType<never>;  // type T6 = never
type T7 = ReturnType<string>;  // Error, type T7 = any
type T8 = ReturnType<Function>;  // Error, type T8 = any

 

 

InstanceType<T>

생성자 함수 타입 T의 인스턴스 타입으로 구성된 타입을 반환한다.

 

ConstructorParameters<T>와 마찬가지로 생성자 함수가 아닌 함수 타입을 전달하면 에러가 발생한다.

class C {
  x = 0;
  y = 0;
}
 
type T0 = InstanceType<typeof C>;  // type T0 = C
type T1 = InstanceType<any>;  // type T1 = any
type T2 = InstanceType<never>;  // type T2 = never
type T3 = InstanceType<string>;  // Error, type T3 = any
type T4 = InstanceType<Function>;  // Error, type T4 = any

 

 

ThisParameterType<T>

함수 타입 T의 매개변수 및 반환 타입에 this 매개변수가 없을 경우 unknown을 반환한다.

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

 

 

OmitThisParameter<T>

함수 타입 T에서 this 매개변수를 제거한 타입을 반환한다.

 

OmitThisParameter<T>을 사용하기 위해서는 strictFunctionTypes 옵션을 활성화해야 한다.

function toHex(this: number) {
  return this.toString(16);
}
 
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
 
console.log(fiveToHex());

 

 

ThisType<T>

ThisType은 새로운 타입을 반환하는 것이 아닌 문맥상 this에 대한 마커 역할을 한다는 점에서 다른 유틸리티 타입들과는 차이가 있다.

 

ThisType<T>을 사용하기 위해서는 noImplicitThis 옵션을 활성화해야 한다.

type MyExclude<T, U extends T> = { [key in keyof T]: T[key] }
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'


type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // this에 메소드 M 타입 지정 && this의 타입을 명시적으로 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; // ThisType으로 바인딩된 this의 x, y 프로퍼티에 접근
      this.y += dy;
    },
  },
});
 
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

 

 

string 타입을 위한 유틸리티 타입

Uppercase<StringType>

문자열 형태의 타입을 전달하면 해당 문자열 값을 모두 대문자로 변환한 새로운 타입을 반환한다.

type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>  // type ShoutyGreeting = "HELLO, WORLD"
 
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">  // type MainID = "ID-MY_APP"

 

 

Lowercase<StringType>

문자열 형태의 타입을 전달하면 해당 문자열 값을 모두 소문자로 변환한 새로운 타입을 반환한다.

type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>  // type QuietGreeting = "hello, world"
 
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">  // type MainID = "id-my_app"

 

 

Capitalize<StringType>

문자열 형태의 타입을 전달하면 해당 문자열 값의 앞 글자를 대문자로 변환한 새로운 타입을 반환한다.

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;  // type Greeting = "Hello, world"

 

 

Uncapitalize<StringType>

문자열 형태의 타입을 전달하면 해당 문자열 값의 앞 글자를 소문자로 변환한 새로운 타입을 반환한다.

type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;  // type UncomfortableGreeting = "hELLO WORLD"