Poznajemy kilka utils, które będą nam potrzebne. Względnie przypominamy je sobie. Także szlifujemy infer. Do dzieła.

Ok, taka funkcja:

function addTwo(x: number, y: number) : number {
    return x + y;
};

Jak wygląda typ tej funkcji? Tak:

function addTwo(x: number, y: number) : number {
    return x + y;
};

type TAddTwo = typeof addTwo;
//type TAddTwo = (x: number, y: number) => number

Mamy parameters i return type, to są util types, które same się opisują, natomiast używamy ich z operatorem typeof:

function addTwo(x: number, y: number) : number {
    return x + y;
};

type TAddTwo = typeof addTwo;
//type TAddTwo = (x: number, y: number) => number

type TAddTwoParams = Parameters<typeof addTwo>
//type TAddTwoParams = [x: number, y: number]

type TAddTwoReturn = ReturnType<typeof addTwo>
//type TAddTwoReturn = number

Te util types świetnie pokazują czym jest infer:

type MyParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Zaraz coś fajnego sobie z inferem napiszemy, ale najpierw jeszcze dwa utilsy. ConstructorParameters:

class C {
  constructor(a: number, b: string) {}
}

type T3 = ConstructorParameters<typeof C>;

//type T3 = [a: number, b: string]

Wiadomo co to robi, natomiast sama konstrukcja może rozwalić głowę:

type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never

Ale spokojnie, pobawimy się trochę więcej TSem to i takie rzeczy ogarniemy. Teraz instance type:

class C {
  x = 0;
  y = 0;
}
 
type T0 = InstanceType<typeof C>;

//type T0 = C

Też może głowę rozwalić, ale innej drogi, aby się tego nauczyć nie ma:

type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any

Ok, fajnie, teraz zabawimy się inferem w takie coś, co ma nam podać typ pierwszego argumentu funkcji:

type GetFirstArgumentOfAnyFunction<T> = T extends (first: infer FirstArgument, ...args: any[]) => any
  ? FirstArgument
  : never;

Przeanalizujmy, moim zdaniem jest proste – infer + label domyśla się jaki jest typ i zapisuje pod takim labelem, który potem zwracamy, chyba że to nie jest funkcja, wtedy zwracamy never.

Zobaczmy:

type GetFirstArgumentOfAnyFunction<T> = T extends (first: infer FirstArgument, ...args: any[]) => any
  ? FirstArgument
  : never;

function addTwo(x: number, y: number) : number {
    return x + y;
};

type addTwoFirstType = GetFirstArgumentOfAnyFunction<typeof addTwo>

//type addTwoFirstType = number

Działa. A gdy nie przyjmuje żadnych argumentów jakaś funkcja, co wtedy? Zobaczmy:

type GetFirstArgumentOfAnyFunction<T> = T extends (first: infer FirstArgument, ...args: any[]) => any
  ? FirstArgument
  : never;

function get42(): number{
    return 42;
}

type get42FirstType = GetFirstArgumentOfAnyFunction<typeof get42>
//type get42FirstType = unknown

Wtedy unknown. Swoją drogą zobaczmy jaki będzie Parameters dla takiej funkcji:

function get42(): number{
    return 42;
}

type get42Params = Parameters<typeof get42>
//type get42Params = []

Ok, spoko. Więcej TSa niedługo!