Website-Suche

typescript - JavaScript-Interface-Schlüssel als String erhalten (wie ein Enum)


Meine Anfrage ist eigentlich ganz einfach, aber ich habe online fast nichts dergleichen gefunden. Stell dir vor, du hast eine Schnittstelle wie diese:

interface MyInterface: {
  propertyA: number;
  propertyB: string;
  propertyC: boolean;
}

Jetzt möchten Sie den Namen eines Feldes verwenden, um dessen Wert dynamisch zuzugreifen, also machen Sie etwas wie object['propertyA'], wobei object vom Typ MyInterface ist.

Allerdings ist es unpraktisch und gefährlich, 'propertyA' als rohen String zu übergeben, da der Code nicht mehr funktioniert, wenn Sie ihn umbenennen oder die Schnittstelle ändern. (Und es wird von TypeScript nicht erkannt, aber ich bin mir nicht sicher, ob das sowieso möglich ist).

Idealerweise würde ich gerne so etwas tun MyInterface.propertyA, ich war traurig, dass dies anscheinend nicht existiert, ich sehe keinen Grund, warum es nicht existieren sollte.

Und jetzt, bevor du sagst, dafür ist object.propertyA da, habe ich das Konzept vereinfacht, um es leicht verständlich zu machen, aber der Anwendungsfall ist nicht genau dieser. Ich könnte Felder eines Interfaces als Strings von React-Komponenten innerhalb des JSX übergeben, um eine wiederverwendbare Methode zu nutzen, oder ich könnte prüfen, ob ein String mit einer Teilmenge von Schlüsseln in einem Interface übereinstimmt, um entsprechend Fehler anzuzeigen.

Die automatische Generierung eines Enums aus dem Interface/Klasse ist keine schlechte Lösung, obwohl sie nicht ideal ist, da sie immer noch einen Overhead verursacht, aber solange es nicht jedes Mal aktualisiert werden muss, wenn sich das Interface ändert, und es nicht vom Typ der Eigenschaften des Interfaces abhängt, ist das nicht schlecht.

Antwort #1

Schnittstellen existieren zur Laufzeit nicht, daher gibt es keine Möglichkeit, die Eigenschaftsnamen einer Schnittstelle als Laufzeitwerte zu erhalten. Was Sie tun können, ist jedoch der umgekehrte Weg: Haben Sie ein "Modell"-Objekt:

const myInterfaceModel = {
    propertyA: 42,
    propertyB: "x",
    propertyC: true,
};

Definiere dann die Schnittstelle unter Verwendung dieses Objekts:

type MyInterface = typeof myInterfaceModel;

Das definiert MyInterface genau so, wie es in Ihrer Frage ist.

Dann können Sie ein Objekt erstellen, das die Eigenschaftsnamen als Zeichenketten hat, so wie dies:

const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) => [key, key])
);

Nun, wenn wir das einfach machen würden, wäre myInterfaceKeys (effektiv) Record<string, string>, was für die Entwicklererfahrung nicht nützlich ist. Aber wir können es besser machen, indem wir einen gemappten Typ darauf anwenden:

type KeyNames<T> = {
    [Key in keyof T]: Key;
};
// ...
const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;

Jetzt stimmt sein Typ mit seinem Laufzeitwert überein, und wir erhalten nützliche Autovervollständigung:

Beispielhafte Verwendung (nur eine grundlegende Funktion, könnte aber genauso gut eine React-Komponente sein):

function example(obj: MyInterface, key: keyof MyInterface) {
    console.log(obj[key]);
}

declare let exampleObject: MyInterface;

example(exampleObject, myInterfaceKeys.propertyA);

Hier ist das alles zusammen (Playground-Link):

type KeyNames<T> = {
    [Key in keyof T]: Key;
};

const myInterfaceModel = {
    propertyA: 42,
    propertyB: "x",
    propertyC: true,
};
type MyInterface = typeof myInterfaceModel;
//   ^? type MyInterface = { propertyA: number, propertyB: string; propertyC : boolean }

const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;

myInterfaceKeys
//             ^−−−− Type a period after this to see the autocompletion

Normalerweise würde ich einfach Zeichenkettenliterale verwenden (example(exampleObject, "propertyA")), da die IDE die passenden Optionen anbietet, und da keyof MyInterface einschränkend ist, würde jeder Code, der eine entfernte Eigenschaft mit einem Zeichenkettenliteral referenziert, nicht mehr kompiliert werden. Aber wenn Sie dieses Objekt mit den Schlüsseln darauf haben möchten, können Sie das oben Genannte machen, um das zu erreichen. (Außerdem habe ich es gerade ausprobiert, und wenn Sie Ihr Objekt der Schlüssel verwenden, funktioniert das Umbenennen einer Eigenschaft überall, während es bei den Zeichenkettenliteralen nicht funktionierte.)

Antwort #2

Suchst du nach so etwas?

enum Direction {
  Up = "u",
  Down = "d",
  Left = "l",
  Right = "r",
}

interface MyDirection  {
    [Direction.Up]:string;
    [Direction.Down]:number;
}

const obj:MyDirection = {
    [Direction.Up]: 'up',
    [Direction.Down]:12,

}


console.log(obj[Direction.Down], Direction.Down)