Hvordan sende typeargumenter til en indeksert type i Typescript?
Jeg prøver å finne ut hvordan jeg kan få returtypen til en generell funksjon i en klasse.
For eksempel,
declare class Test {
open<T, R>(t1: T, t2: R): T | R;
}
type TestType<T, R> = (Test['open'])<T, R>;
type TestTypeReturn = ReturnType<TestType<number, number>>;
Jeg får imidlertid feil på den fjerde ikke-tomme linjen rundt ...])<T, R> som sier at et semikolon forventes. Jeg er ikke sikker på om det jeg ønsker å gjøre er mulig. Er det noen som vet?
Svar #1
Antageligvis for det spesifikke eksemplet som vises, kunne du manuelt skrive ut den ønskede typen, som:
type TestTypeManual<T, R> = (t1: T, t2: R) => T | R;
type TestTypeReturnManual = ReturnType<TestTypeManual<number, number>>; // number
Men du vil at kompilatoren skal gjøre dette for deg.
TypeScripts typesystem mangler uttrykksevnen som trengs for å representere forholdet mellom en konkret typealias som refererer til en generisk funksjonstype som type GenFunc=<T>(x: T)=>T og en generisk typealias som refererer til en konkret funksjonstype som type GenAlias<T>=(x:T)=>T. Du kan ikke spesifisere typeparameter(ene) til en generisk funksjon uten å kalle funksjonen. Så med det ovenstående, selv om du kan kalle en funksjon av typen GenFunc som genFunc<string>("hello"), finnes det ingen slik type som GenFunc<string>. Det finnes et åpent forslag (microsoft/TypeScript#17574) som kanskje lar slik manipulasjon av høyere rangtype, men jeg vet ikke om vi kan forvente noen fremgang der.
Foreløpig ser det ikke ut til å være en måte å gjøre dette ren på typenivå.
Hvis du ikke har noe imot noen mulige hacks som har en viss effekt på den genererte JavaScript-en, kan du dra nytte av noe støtte for å inferere generiske funksjoner som ble lagt til i TypeScript 3.4. Slik kan jeg gjøre det. Først, introduser en funksjonsdeklarasjon et sted som dette:
// you could just declare this instead of implementing it, but whatever:
function magicThunk<A extends any[], R>(f: (...args: A) => R): () => (...args: A) => R {
return () => f;
}
Funksjonen magicThunk() tar antageligvis bare en callback-funksjon f og returnerer en ny funksjon uten argumenter som returnerer f, noe som ikke virker særlig magisk (men er i det minste en thunk). Men hvis du kaller magicThunk() med en generisk funksjon, skjer det en interessant ting fra og med TS3.4:
const genericIdentity = <T>(x: T) => x; // <T>(x: T) => T
const thunkIdentity = magicThunk(genericIdentity); // <T>() => (x: T) => T
Den returnerte funksjonen er også generisk, men den generiske typeparameteren/parametrene har blitt flyttet ut ett nivå. Nå kan du kalle den utsatte generiske funksjonen og angi dens typeparameter/parametre, og få en ikke-generisk funksjon:
const stringIdentity = thunkIdentity<string>(); // (x: string) => string;
Så dette er akkurat operasjonen med «å spesifisere typeparametrene til en generisk funksjon uten å kalle den», slik vi ønsker.
Uansett, vi er nesten der... det siste steget er å lage en ny generisk klasse, som lar oss få noen typeparametere i omfang uten å sette dem direkte på en annen funksjon. Slik ville jeg gjort det for din Test["open"]-type:
class TransformTest<T, R> {
open = magicThunk(new Test().open)<T, R>()
}
Og nå bør typen til TransformTest<T, R>.open være typen du leter etter:
type TestType<T, R> = TransformTest<T, R>["open"]; // (t1: T, t2: R) => T | R
type TestTypeReturn = ReturnType<TestType<number, number>>; // number
Slik at alt fungerer. Legg merke til at du aldri faktisk trenger å kalle magicThunk() eller opprette en instans av TransformTest i koden din (det er bra fordi jeg tror ikke open vil bli riktig bundet); kompilatoren gjør alt arbeidet du trenger på design-tidspunktet; den må bare tvinges til å gå gjennom bevegelsene for å evaluere typene av koden som vil bli generert.
Ok, håper det hjelper; lykke til!
Lenke til kode
