Hur man annoterar en funktion med ett generiskt returvärde i TypeScript
Givet en funktion som returnerar en fabrik, hur kan jag annotera funktionen/fabriken så att den innehåller rätt typdefinitioner?
Här är mitt exempel:
class item<T> {
constructor(a: T) {
this.a = a;
}
a: T
}
function generate(c) {
return function a(a) {
return new c(a);
}
}
const factory = generate(item); // I want this to always be annotated as <T> (a: T) => item<T>
const instance = factory('string'); // instance should now be of type item<string>
Är detta möjligt i TypeScript eller borde jag föreslå det som en ny funktion?
Svar #1
För icke-generiska klasser kan vi i 3.0 använda Tuples i restparametrar och spread-uttryck samt InstanceType för att mappa konstruktören till en liknande funktion.
För generiska klasser finns det tyvärr inget sätt att bevara typargumentet vid mappning. Den enda lösningen är att lägga till ett fält i klassen som kommer att tala om för generate vilken resultattyp som ska användas. Detta kan göras genom sammanslagning av gränssnitts- och klassdefinitioner så att den ursprungliga klassen inte vet något om generate.
En möjlig lösning som använder båda metoderna (automatisk där det är möjligt, manuell där det är nödvändigt) skulle kunna se ut ungefär så här:
class item<T> {
constructor(a: T) {
this.a = a;
}
a: T
}
const generateResult = Symbol.for("generate")
interface item<T> {
[generateResult] : <T>(a: T) => item<T>
}
type d = item<any>[typeof generateResult]
type SupportsGeneration<R> = { [generateResult] : R }
type ConstructorArguments<T> = T extends new (...a:infer A ) => any ? A : [];
function generate<T extends { new (...a:any[]) : SupportsGeneration<any> }>(c:T) : InstanceType<T> extends SupportsGeneration<infer R> ? R: never
function generate<T extends new (...a:any[]) => any>(c:T) : (a: ConstructorArguments<T>) => InstanceType<T>
function generate(c: new (...a: any[]) => any) : any {
return function a(...a: any[]) {
return new c(...a);
}
}
const factory = generate(item);
const instance = factory('string');
Svar #2
Det verkar vara möjligt att uppnå önskad effekt sedan v3.4:
class item<T> {
constructor(a: T) {
this.a = a;
}
a: T
}
function generate<Arg, Result>(c: new (arg: Arg) => Result) {
return function a(a: Arg) {
return new c(a);
}
}
const factory = generate(item); // I want this to always be annotated as <T> (a: T) => item<T>
const instance = factory('string'); // instance should now be of type item<string>
Lekplats
