Javascript

TypeScript 타입 추론의 새로운 지평: infer의 매력

드리프트2 2024. 6. 16. 18:29

 

TypeScript에서 infer는 마치 마법 주문처럼 타입 추론의 범위를 넓혀줍니다.

 

덕분에 더욱 정확하고 유연한 타입을 정의할 수 있게 되었죠.

 

하지만 infer는 늘 있었던 것은 아닙니다.

 

최근에 더욱 주목받는 이유는 무엇일까요?

infer를 이해하기 위한 여정: Conditional Type부터 시작

infer를 처음 접했을 때, 저는 갑작스러운 등장에 당황했고, 제대로 활용하는 방법을 몰라 이해하는 데 시간이 걸렸습니다.

 

infer를 제대로 이해하기 위해 먼저 Conditional Type에 대한 기본적인 내용을 복습했습니다.

 

아래 코드는 TypeScript 공식 문서에서 가져온 것입니다.

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}

type Example1 = Dog extends Animal ? number : string;
type Example2 = RegExp extends Animal ? number : string;

 

이 삼항 연산자처럼 생긴 것이 바로 Conditional Type입니다.

 

Conditional Type:

T extends U ? X : Y
  • T extends U는 T 타입이 U 타입의 서브타입(즉, U 타입에 할당 가능)인지 확인합니다.
  • ? X : YT extends U가 true이면 X 타입을 반환하고, 아니면 Y 타입을 반환합니다.

즉, T 타입이 U 타입에 포함되어 있으면 X 타입을 반환하고, 포함되어 있지 않으면 Y 타입을 반환합니다.

 

infer의 등장: 타입 추론의 진화

Conditional Type을 이해한 후, infer에 대한 이해를 다시 시작했습니다.

 

Conditional Type:

T extends U ? X : Y
  • inferU 부분에서 사용하는 키워드입니다.

infer는 TypeScript의 Conditional Type에서 사용되는 특수 키워드입니다.

 

Conditional Type은 특정 조건을 만족하는지 확인하고, 그 결과에 따라 다른 타입을 반환할 수 있는 기능입니다.

 

이는 프로그래밍의 if 문과 유사하지만, 타입에 대해 수행합니다.

 

infer 키워드는 Conditional Type 내에서 사용되며, 특정 타입에서 다른 타입을 "추론"(즉, 추출)하는 데 사용됩니다.

 

예를 들어, 함수의 반환 값 타입을 알고 싶을 때 infer를 사용할 수 있습니다.

 

TypeScript 공식 문서의 코드를 다시 살펴보았습니다.

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;

type Num = GetReturnType<() => number>;

type Str = GetReturnType<(x: string) => string>;

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;

 

확실히 U 부분이 (...args: never[]) => infer Return으로 되어 있습니다.

 

위 코드는 Type(T)가 "(인수를 받지 않는) 함수"인지 확인합니다.

 

Type이 함수이면, 해당 함수의 반환 값 타입을 infer Return으로 추출하여 Return 타입을 반환합니다.

 

Type이 함수가 아니면 never 타입을 반환합니다.

 

참고로, 인수를 받는 함수의 경우 ...args: any[]가 됩니다.

 

덧붙이자면, ...argsarguments 객체로 JavaScript에서도 사용됩니다.

 

https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/arguments

 

arguments 객체 - JavaScript | MDN

arguments 객체는 함수에 전달된 인수에 해당하는 Array 형태의 객체입니다.

developer.mozilla.org

 

 

arguments는 배열처럼(Array-like) 동작하는 객체로 함수에 전달된 인수 값을 포함하고 있으며, 함수 내에서 접근할 수 있습니다.

 

또 다른 샘플 코드를 살펴보았습니다.

 

다시 한번 TypeScript 공식 문서의 코드를 확인했습니다.

type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U
  : T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string

 

 

T extends (infer U)[]

 

T 타입이 배열 타입인지 확인합니다.

 

배열 타입이면 해당 배열의 요소 타입을 infer U로 추출하고, U 타입을 반환합니다.

T extends (...args: any[]) => infer U

 

함수 타입인지 확인합니다.

T extends Promise

 

Promise 타입인지 확인합니다.

 

string 타입은 배열 타입도, 함수 타입도, Promise 타입도 아니기 때문에 그대로 타입을 반환합니다.

 

그래서 아래 코드는 string을 반환합니다.

type T0 = Unpacked; // string

 

infer의 등장: 타입 추론의 새로운 지평을 열다

infer는 TypeScript 2.8 버전에서 처음 등장했습니다.

 

이전에는 typeofkeyof 등으로 제한적인 타입 추론만 가능했지만, infer제네릭 타입에서 특정 부분을 추론할 수 있도록 힘을 실어줍니다.

 

예를 들어, Promise 타입의 T를 추론하는 경우를 살펴봅시다.

type PromiseType<T> = T extends Promise<infer U> ? U : never; 

 

Promise<infer U>를 통해 Promise 타입의 제네릭 파라미터 U를 추론하여 PromiseType을 정의할 수 있습니다.

infer를 활용한 다양한 패턴

infer의 등장은 TypeScript 개발자들에게 새로운 가능성을 열어주었습니다.

  • 조건부 타입을 활용한 다양한 타입 변환: Promise 타입 뿐 아니라, 다양한 타입과 함께 infer를 사용하여 원하는 타입을 추출하고 변환할 수 있습니다.
  • 복잡한 타입 추론: infer를 활용하여 깊이 중첩된 객체의 타입을 추론하거나, 함수의 인자와 반환 값의 타입을 정확하게 추론할 수 있습니다.
  • 타입 안전성 향상: infer를 통해 타입 추론을 강화함으로써, 개발 중 발생할 수 있는 타입 오류를 미리 방지하고 코드의 안전성을 높일 수 있습니다.

infer와 함께 발전하는 TypeScript 생태계

infer의 등장은 TypeScript 생태계에 큰 영향을 미쳤습니다.

  • 타입 유틸리티 라이브러리의 발전: infer를 활용한 다양한 유틸리티 타입이 개발되어, 개발자들이 더욱 편리하게 타입 작업을 수행할 수 있도록 돕고 있습니다.
  • 더욱 강력해진 타입 정의: infer를 통해 더욱 정확하고 유연한 타입을 정의할 수 있게 되면서, TypeScript 코드의 가독성과 유지보수성이 향상되었습니다.

마무리

infer는 TypeScript의 타입 추론 기능을 한 단계 끌어올린 핵심 기능입니다.

 

infer를 활용하면 더욱 정확하고 안전한 TypeScript 코드를 작성할 수 있으며, TypeScript 생태계의 발전에도 크게 기여하고 있습니다.

 

infer를 활용하여 타입 추론의 즐거움을 경험해 보세요!