javascript - Typescript Karta över godtyckliga generiska typer
Jag försöker definiera två typer, som borde se ut ungefär så här:
export type IQuery<P, U> = {
request: string;
params: (props: P, upsteam?: U) => object;
key: (props: P, upstream?: U) => string;
forceRequest: boolean;
depends?: QueryMap
}
export type QueryMap = {
[k: string]: IQuery
};
De begränsningar jag försöker uttrycka är att params och key har samma typer för sina två argument, och att en QueryMap bara är en mappning från en sträng till en godtycklig IQuery (det spelar ingen roll vilka typerna är). Kompilatorn klagar här eftersom den vill att en typ ska specificeras för IQuery, men poängen är att varje IQuery i mappen ska kunna parametriseras oberoende. Finns det något sätt att uttrycka detta i TypeScript?
Dessutom, om möjligt, skulle jag vilja få information/garantier om formen på de uppströms QueryMap-objekt som finns i IQuery när jag itererar genom detta träd.
Svar #1
Det enklaste du kan göra är detta:
export type QueryMap = {
[k: string]: IQuery<any, any>
};
Det är inte helt typ-säkert, men det är inte alltför långt ifrån det du försöker representera. Om du inte vill förlora typinformation för ett värde av typen QueryMap, låt kompilatorn härleda en smalare typ och använd en generisk hjälpfunktion för att säkerställa att det är en giltig QueryMap, så här:
const asQueryMap = <T extends QueryMap>(t: T) => t;
const queryMap = asQueryMap({
foo: {
request: "a",
params(p: string, u?: number) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
});
Värdet queryMap.foo.params är fortfarande känt som en metod som accepterar en string och ett valfritt number, även om typen QueryMap['foo']['params'] inte är det.
Om du specificerar något som inte kan tilldelas en QueryMap får du ett fel:
const bad = asQueryMap({
foo: {
request: "a",
params(p: string, u?: number) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
},
bar: {
request: 123,
params(p: number, u?: string) {return {}},
key(p: number, u?: string) {return "nope"},
forceRequest: false
}
}); // error! bar.request is a number
Det inte helt typ-säkra problemet visas här:
const notExactlySafe = asQueryMap({
baz: {
request: "a",
params(p: number, u?: string) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
});
Detta accepteras, även om det inte finns några konsekventa rimliga värden på P och U som fungerar här (vilket är vad som händer när du använder any). Om du behöver begränsa detta mer kan du försöka få TypeScript att härleda uppsättningar av P- och U-värden från värdet eller varna dig om det inte går, men det är inte enkelt.
För fullständighetens skull, här är hur jag skulle göra det... använd villkorliga typer för att härleda P och U för varje element i din QueryMap genom att inspektera params-metoden, och kontrollera sedan att key-metoden matchar den.
const asSaferQueryMap = <T extends QueryMap>(
t: T & { [K in keyof T]:
T[K]['params'] extends (p: infer P, u?: infer U) => any ? (
T[K] extends IQuery<P, U> ? T[K] : IQuery<P, U>
) : never
}
): T => t;
Nu kommer följande fortfarande att fungera:
const queryMap = asSaferQueryMap({
foo: {
request: "a",
params(p: string, u?: number) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
});
medan detta nu kommer att vara ett fel:
const notExactlySafe = asSaferQueryMap({
baz: {
request: "a",
params(p: number, u?: string) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
}); // error, string is not assignable to number
Detta ökar din typ-säkerhet marginellt på bekostnad av en ganska komplicerad bit typmanipulation i typen av asSaferQueryMap(), så jag vet inte om det är värt det. IQuery<any, any> är förmodligen tillräckligt för de flesta ändamål.
Okej, hoppas det hjälper; lycka till!
Svar #2
Du kan använda IQuery<any, any>.
Jag är inte säker på vad du hoppas på i den andra delen av frågan. TypeScript ger dig inte typinformation vid körning. Om du bara vill ha typvariabler att referera till medan du manipulerar en enda IQuery, kan du skicka en IQuery<any, any> till en function myFunction<P, U>(iquery: IQuery<P, U>) { ... }.
