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!