11.1 유틸리티 타입의 개념

유틸리티 타입(Utiltiy Types)이란 일반적인 타입 변환을 쉽게 할 수 있도록 타입스크립트에서 전역으로 제공하는 유용한 타입들을 지칭합니다. 예를 들면, 추가적인 타입 정의 없이 모든 프로퍼티를 선택적 또는 Readonly 프로퍼티로 변경한 새로운 타입을 생성하거나, 기존에 정의된 타입을 프로퍼티/타입을 선택 또는 제외하여 재사용할 수 있습니다. 그리고 함수와 클래스의 매개변수의 타입 또한 튜플 형태로 뽑아내는 등의 편리한 기능을 제공합니다. 유틸리티 타입은 앞서 우리가 배웠던 타입 문법을 활용해 충분히 구현할 수 있습니다. 하지만 타입스크립트로 프로젝트를 진행하며 자주 사용되는 타입 변환을 타입 별칭으로 미리 정의되어 편리하게 사용할 수 있습니다. 유틸리티 타입 구현체는 우리가 npm을 통해 설치한 타입스크립트 lib 폴더의 lib.es5.d.ts 파일에서 찾아볼 수 있습니다. (node_modules > typescript > lib > lib.es5.dts)

유틸리티 타입을 사용하면 중복된 타입 선언 코드를 피할 수 있고, 유지 보수도 더욱 편리해집니다. 또한 원하는 타입을 정확하게 설정하여 컴포넌트, 함수의 안정성과 사용성을 높이고 예상치 못한 런타임 에러를 줄일 수 있습니다.

| 유틸리티 타입 | 설명 (대표 타입) | Released /Update | | --- | --- | --- | | Awaited<Type> | 비동기 함수의 await 또는 프로미스의 .then 메서드같이 재귀적으로 프로미스를 풀어내는 작업에서, 프로미스의 결괏값 타입을 새로운 타입으로 반환 (프로미스) | 4.5 | | ✅ Partial<Type> | 주어진 타입의 모든 프로퍼티를 선택적 프로퍼티를 가지는 새로운 타입을 반환 (인터페이스) | 2.1 | | ✅ Required<Type> | 주어진 타입의 모든 프로퍼티를 필수 프로퍼티를 가지는 새로운 타입을 반환  (인터페이스) | 2.8 | | ✅ Readonly<Type> | 주어진 타입의 모든 프로퍼티를 Readonly 프로퍼티를 가지는 새로운 타입을 반환  (인터페이스) | 2.1 | | ✅ Record<Keys, Type> | 일괄 기록할 프로퍼티 키로 Keys를, 값으로 Type을 갖는 새로운 타입을 반환  (인터페이스) | 2.1 | | ✅ Pick<Type, Keys> | 주어진 타입 프로퍼티 중에서 Keys에 포함된 프로퍼티만 뽑아 새로운 타입을 반환 (인터페이스) | 2.1 | | ✅ Omit<Type, Keys> | 주어진 타입에서 Keys에 포함된 프로퍼티를 제외한 나머지 프로퍼티를 가진 새로운 타입을 반환 (인터페이스) | 3.5 | | ✅ Exclude<Type, ExcludedUnion> | 주어진 타입에서 ExcludedUnion를 제외한 새로운 타입 반환 (유니언) | 2.8 | | ✅ Extract<Type, Union> | 주어진 타입에서 Union을 추출한 새로운 타입 반환 (유니언) | 2.8 | | NonNullable<Type> | 주어진 타입에서 null과 undefind를 제외한 새로운 타입 반환(유니언) | 2.8 | | Parameters<Type> | 주어진 함수의 매개변수 타입을 새로운 튜플 타입으로 반환 (함수, 튜플) | 3.1 | | ConstructorParameters<Type> | 주어진 클래스의 매개변수 타입을 새로운 튜플 타입으로 반환 (클래스, 튜플) | 3.1 | | ✅ ReturnType<Type> | 주어진 함수의 반환(return) 타입을 새로운 타입으로 반환 (함수) | 2.8 | | InstanceType<Type> | 주어진 클래스의 인스턴스 타입을 새로운 타입으로 반환 (클래스) | 2.8 | | ThisParameterType<Type> | 주어진 함수의 명시적 this 매개변수 타입을 새로운 타입으로 반환 (함수) | 3.3 | | OmitThisParameter<Type> | 주어진 함수의 명시적 this 매개변수 타입을 제거한 새로운 타입 반환 (함수) | 3.3 | | ThisType<Type> | 주어진 타입의 this 컨텍스트를 명시하고 별도의 타입 반환 없음 (인터페이스) | 2.3 | | Uppercase<StringType> | 문자열의 각 문자를 대문자로 변환 (String) | 4.1 | | Lowercase<StringType> | 문자열의 각 문자를 소문자로 변환 (String) | 4.1 | | Capitalize<StringType> | 문자열의 첫 번째 문자를 대문자로 변환 (String) | 4.1 | | Uncapitalize<StringType> | 문자열의 첫 번째 문자를 소문자로 변환 (String) | 4.1 |

타입스크립트 공식 문서에서 소개하는 총 21 가지의 유틸리티 타입입니다. 다양한 유틸리티 타입 중에서 자주 사용되는 유틸리티 타입(✅)을 중점으로 알아봅시다. 위의 유틸리티 타입은 타입스크립트 공식 문서 그대로 표기했습니다. 다음 절부터는 앞선 단원들과 마찬가지로 편의상 타입 변수를 T, 또 다른 타입은 U를 사용하도록 하겠습니다.

<aside> 💡 내장 문자열 조작 타입(Intrinsic String Manipulation Types)

내장 문자열 조작 타입인 Uppercase, Lowercase, Capitalize, Uncapitalize 타입은 이름 그대로 문자열 조작을 위해 만들어진 타입이며, 성능을 위해 컴파일러에 기본 내장되어 있어 다른 유틸리티 타입들과 달리 구현부를 타입스크립트 라이브러리 .d.ts 파일에서는 찾을 수 없습니다.

내장 문자열 조작 타입의 구현부는 아래 코드와 같습니다. 이 내장 타입들은 타입스크립트 4.1 이후로 자바스크립트 문자열 런타임 함수를 직접 사용함으로써 언어, 지역 설정, 출력 형식, 통화 형식 등의 로케일(locale) 설정을 고려하지 않고 문자열을 처리합니다.

// 출처: TypeScript Documentation (Template Literal Types)
function applyStringMapping(symbol: Symbol, str: string) {
    switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
        case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
        case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
        case IntrinsicTypeKind.Capitalize:
			       return str.charAt(0).toUpperCase() + str.slice(1);
        case IntrinsicTypeKind.Uncapitalize:
			       return str.charAt(0).toLowerCase() + str.slice(1);
    }
    return str;
}

</aside>

<aside> 💡 커스텀 유틸리티 타입 (Custom Utility type) 타입을 정확하게 설정함으로써 컴포넌트 및 함수의 안정성과 사용성을 높일 수 있습니다. 하지만 타이핑을 하다 보면 표현하기 힘든 타입을 마주쳤을 때, 기존 유틸리티 타입만으로 표현하는 데 한계를 느낄 수 있습니다. 이런 경우 유틸리티 타입을 활용한 커스텀 유틸리티 타입을 만들어 문제를 해결할 수 있습니다. 물론 커스텀 유틸리티 타입 함수를 구현하기란 쉬운 일이 아닙니다. 하지만 어떤 타입을 구현해야 하는지 정확히 파악하고, 필요한 타입을 세분화하여 단계적으로 구현해나가다 보면 원하는 타입을 표현할 수 있을 것입니다.

아래 예시는 여러 프로퍼티 중 하나의 프로퍼티만 받는 PickOne 타입입니다. 사용자가 서비스 상에서 account 또는 card 중 오직 하나의 방식으로만 결제를 진행하도록 하는 로직을 위해 작성한 타입이죠. { account: string } | { card: string }으로 구현하면 account와 card 프로퍼티을 모두 가진 객체도 허용되어 원하는 기능을 구현할 수 없습니다. 최종 결제 방식이 선택되면 다른 하나의 방식은 존재하더라도 undefined로 바꿔주어야 합니다.

// 출처: 우아한 타입스크립트 with 리액트 p.168
type PickOne<T> = {
    [P in keyof T]:
		    Record<P, T[P]> & Partial<Record<Exclude<keyof T, P>, undefined>>;
}[keyof T];

</aside>

11.2 Partial, Required, Readonly

11.2.1 Partial

Partial<T> 는 내장 유틸리티 타입 중 하나로, 특정 객체의 프로퍼티를 선택적(Optional) 프로퍼티로 만드는 역할을 합니다. 즉, 제네릭 타입 T 가 주어지면 T의 모든 프로퍼티가 선택적으로 변경됩니다. 이는 옵셔널 체이닝을 사용하지 않고 주어진 객체의 모든 프로퍼티를 선택적으로 변경할 수 있습니다.

interface Developer {
	name: string;
	age: number;
	skill: string[];
}

Developer 타입은 name , age , skill 3가지의 프로퍼티를 가지고 있습니다. 위 프로퍼티들은 필수적이지만 Partial<Developer> 을 사용하면 Developer의 모든 프로퍼티를 선택적으로 변경할 수 있습니다.

type PartialDeveloper = Partial<Developer>;
// { name?: string; age?: number; skill?: string[]; }

PartialDeveloper 타입의 객체를 생성할 때 모든 프로퍼티를 제공하지 않아도 되므로, 일부 프로퍼티로 객체를 생성하고 업데이트하는 등의 작업을 유연하게 수행할 수 있습니다.

11.2.2 Required

Required<T> 는 특정 객체의 프로퍼티를 필수적으로 만드는 역할을 합니다. 즉, 제네릭 타입 T 가 주어지면 T의 모든 프로퍼티를 필수적으로 변경합니다.