챌린지

주어지는 함수 타입 Fn이 있고 any 타입 A(이 경우 any의 의미는 타입이 정해지지 않았고, 실제로 어떤 타입이든 상관없다는 의미입니다.)가 있습니다. 이 때 Fn을 첫 번째 매개변수로 받고, A를 두번째로 받아서 Fn의 마지막 매개변수로 A가 추가 된 형태의 함수 타입 G를 만드는 제네릭 타입을 만들어 보세요.

예시:

type Fn = (a: number, b: string) => number;

// expected be (a: number, b: string, x: boolean) => number
type Result = AppendArgument<Fn, boolean>;

해답

흥미로운 챌린지입니다. 타입 추론, 가변 인자 튜플 타입(variadic tuple), 조건부 타 입 등 흥미로운 요소들을 다뤄볼 수 있습니다.

먼저 함수의 매개변수와 반환 타입을 추론하는 것부터 시작하겠습니다. 이 경우 조건 부 타입이 유용하게 쓰입니다. 타입을 추론하고 나면, 지금 단계에서는 입력으로 주어 진 것을 복사한 형태의 함수 시그니처를 반환해 줍니다:

type AppendArgument<Fn, A> = Fn extends (args: infer P) => infer R
  ? (args: P) => R
  : never;

아직 해답은 전혀 충분하지 않습니다. Fn에 할당 가능한 함수는 매개변수로 args 하나만을 가지는 함수이기 때문입니다. 이건 의도와 맞지 않습니다. 매개변수가 없거 나 하나 이상인 경우도 고려될 수 있어야 합니다.

이 문제를 해결하기 위해 스프레드 연산자(spread operator)를 사용할 수 있습니다:

type AppendArgument<Fn, A> = Fn extends (...args: infer P) => infer R
  ? (args: P) => R
  : never;

이제 조건부 타입의 조건이 참으로 평가됩니다. 이 경우 참인 경우의 분기를 타기 때 문에 타입 매개변수 P(함수의 매개변수)와 타입 매개변수 R(반환 타입)을 사용할 수 있습니다. 여전히 남아있는 문제가 있긴 합니다. 타입 매개변수 P는 튜플의 형태 이지만 이 매개변수들을 각각 분리해서 다뤄주어야 할 필요가 있습니다.

가변 인자 튜플 타입(variadic tuple type)을 사용하여 튜플을 쪼갤 수 있습니다(역주
본 문단의 과정이 없어도 답을 구할 수 있습니다. 이후 과정에서 얻은 튜플을 변형 하여 사용하지 않을 것이기 때문입니다.):
type AppendArgument<Fn, A> = Fn extends (...args: [...infer P]) => infer R
  ? (args: P) => R
  : never;

타입 매개변수 P는 이제 우리가 원하는 형태입니다. 남은 것은 추론된 타입을 통해 새로운 함수 시그니처를 만드는 작업입니다:

type AppendArgument<Fn, A> = Fn extends (...args: [...infer P]) => infer R
  ? (...args: [...P]) => R
  : never;

이제 입력으로 주어진 함수를 받아서 타입을 추론한 뒤 새로운 함수를 반환해주는 타 입을 만들었습니다. 이 작업이 끝난 뒤 매개변수 A를 매개변수들의 배열에 넣어주기 만 하면 됩니다:

type AppendArgument<Fn, A> = Fn extends (...args: [...infer P]) => infer R
  ? (...args: [...P, A]) => R
  : never;

참고