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