Typescript: Wie schreibt man einen Typwächter, um zu erkennen, ob ein Wert Mitglied einer Vereinigung ist?
Ich habe einen Typ, der so definiert ist:
export type Thing = "thinga" | "thingb"
Gibt es eine Möglichkeit herauszufinden, ob ein beliebiger Zeichenfolgenwert, den ich habe, Teil dieser Vereinigung ist?
let value1 = "thinga";
let value2 = "otherthing";
console.log("value1: " + value1 <is in> Thing );
console.log("value2: " + value2 <is in> Thing );
Wo ich möchte, dass es für value1 wahr ist, aber für value2 falsch. Der eigentliche Anwendungsfall ist, dass ich ein Array von Zeichenketten habe, das ich herausfiltern möchte, wenn sie nicht in der Definition sind (und möglicherweise protokollieren) und dann in den richtigen Typ umwandeln.
Das Problem ist, dass der Thing-Typ generierter Code ist, der zusammen mit meinem Code kompiliert wird, und die Werte sich ändern können - deshalb möchte ich keine bedingte Logik in einem Typwächter schreiben, die fest auf den Wert codiert ist. Ich habe keine Kontrolle über das, was den Code generiert - ansonsten würde ich erraten, dass das, was ich in diesem Fall tun möchte, einfach wäre, wenn der Typ stattdessen ein String-Enum wäre.
Ich hoffe, dass ich eine Art Typschutz schreiben kann, der keyof oder etwas Ähnliches verwendet? Aber ich sehe nicht, wie ich ihn zum Funktionieren bringen kann.
Die TypeScript-Version ist 2.8.1, obwohl ein Upgrade kein großes Problem darstellt.
Ich bin mir dieser Frage bewusst: https://stackoverflow.com/a/50085718/924597, aber ich denke nicht, dass dies ein Duplikat ist, weil ich die Antwort auf diese Frage nicht verwenden kann.
Antwort #1
Es gibt keine Möglichkeit zur Laufzeit, auf die Werte in der Vereinigung von String-Literaltypen zuzugreifen, da alle Typinformationen während der Kompilierung gelöscht werden. Wir können ein Array mit den Werten so konstruieren, dass ein Kompilierungsfehler auftritt, wenn sich der Vereinigungstyp ändert.
export type Thing = "thinga" | "thingb"
function getAllValues<T>() {
class Helper<TOriginal, TLeft extends TOriginal> {
private data: TOriginal[] = [] ;
private dummy!: TLeft;
done<TThis extends Helper<any, never>>(this: TThis) : TOriginal[]{
return this.data;
}
push<TValue extends TLeft>(value: TValue) : Helper<TOriginal, Exclude<TLeft, TValue>> {
this.data.push(value);
return this as any;
}
}
return new Helper<T, T>();
}
let allValues = getAllValues<Thing>().push("thinga").push("thingb").done(); //Ok
let oldValues = getAllValues<Thing>().push("thinga").push("thingb").push("oldThing").done(); // error oldThinig is not part of the union
let missingValues = getAllValues<Thing>().push("thinga").done(); // error we are missing a value
Jetzt, da wir alle Werte haben, ist das Erstellen eines Typ-Guards einfach:
const isThing = (function(){
let allValues = getAllValues<Thing>().push("thinga").push("thingb").done();
return function(s: string) : s is Thing {
return allValues.indexOf(s as Thing) != -1;
}
})()
let value1 = "thinga";
let value2 = "otherthing";
console.log("value1: " + isThing(value1) );
console.log("value2: " + isThing(value2) );
