유틸리티 타입
유틸리티 타입 (Utility Types)
TypeScript는 흔히 반복되는 타입 변환을 쉽게 처리하도록 몇 가지 유틸리티 타입을 제공해요. Awaited나 Partial, Record처럼 이미 전역에 포함되어 있어서 import 없이 바로 쓸 수 있죠. 이 글에서는 각 유틸리티 타입이 어떤 변환을 수행하는지, 그리고 언제 쓰면 좋은지 예시와 함께 살펴볼게요.
출처: TypeScript 핸드북
Awaited<Type>
출시: 4.5
이 타입은 async 함수에서의 await나 Promise의 .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에서 null과 undefined를 제외해 타입을 구성해요.
예시
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는 타입 시스템 안에서 문자열 조작에 쓸 수 있는 타입 집합을 포함해요. 자세한 내용은 템플릿 리터럴 타입 문서에서 확인할 수 있어요.