Erklären Sie TypeScript-zugeordnete Typen und eine bessere wörtliche Typinferenz.

Erklären Sie TypeScript-zugeordnete Typen und eine bessere wörtliche Typinferenz.

Überblick

TypeScript 2.1 führte zugeordnete Typen ein, eine leistungsstarke Ergänzung des Typsystems. Im Wesentlichen ermöglichen uns zugeordnete Typen, durch die Zuordnung von Attributtypen neue Typen aus vorhandenen Typen zu erstellen. Konvertiert jede Eigenschaft eines vorhandenen Typs gemäß den von uns angegebenen Regeln. Die konvertierten Attribute bilden den neuen Typ.

Mithilfe zugeordneter Typen können Sie die Auswirkungen von Methoden wie Object.freeze() im Typsystem erfassen. Sobald ein Objekt eingefroren ist, können Sie keine Eigenschaften mehr hinzufügen, ändern oder löschen. Sehen wir uns an, wie dies im Typsystem codiert werden kann, ohne zugeordnete Typen zu verwenden:

Schnittstelle Punkt {
  x: Zahl;
  y: Zahl;
}

Schnittstelle FrozenPoint {
  schreibgeschützt x: Zahl;
  schreibgeschützt y: Zahl;
}

Funktion freezePoint(p: Punkt): FrozenPoint {
  gibt Objekt.freeze(p) zurück;
}

const Herkunft = freezePoint({ x: 0, y: 0 });

// Fehler! Kann 'x' nicht zuweisen, da es
// ist eine Konstante oder eine schreibgeschützte Eigenschaft.
Ursprung.x = 42;

Wir definieren eine Point-Schnittstelle, die zwei Eigenschaften enthält, x und y. Wir definieren außerdem eine weitere Schnittstelle, FrozenPoint, die mit Point identisch ist, außer dass alle ihre Eigenschaften mit readonly als schreibgeschützte Eigenschaften definiert sind.

Die Funktion „freezePoint“ akzeptiert einen Punkt als Argument, friert ihn ein und gibt dann dasselbe Objekt an den Anrufer zurück. Der Typ des Objekts wurde jedoch in FrozenPoint geändert, sodass seine Eigenschaften statisch als schreibgeschützt typisiert sind. Aus diesem Grund tritt bei dem Versuch, der Eigenschaft x den Wert 42 zuzuweisen, ein Fehler in TypeScript auf. Zur Laufzeit wird die Zuweisung entweder einen TypeError auslösen (strikter Modus) oder stillschweigend fehlschlagen (nicht strenger Modus).

Das obige Beispiel lässt sich zwar kompilieren und funktioniert korrekt, weist jedoch zwei wesentliche Nachteile auf:

  • Es werden zwei Schnittstellen benötigt. Zusätzlich zum Typ „Point“ müssen Sie auch den Typ „FrozenPoint“ definieren, damit der Modifikator „readonly“ zu beiden Eigenschaften hinzugefügt werden kann. Wenn wir Point ändern, müssen wir auch FrozenPoint ändern, was fehleranfällig und ärgerlich ist.
  • Die Funktion freezePoint ist erforderlich. Für jeden Objekttyp, den wir in unserer Anwendung einfrieren möchten, müssen wir eine Wrapper-Funktion definieren, die ein Objekt dieses Typs akzeptiert und ein Objekt des eingefrorenen Typs zurückgibt. Ohne einen zugeordneten Typ können wir Object.freeze() nicht statisch auf generische Weise verwenden.

Verwenden zugeordneter Typen zum Erstellen von Object.freeze()

Sehen wir uns an, wie Object.freeze() in der Datei lib.d.ts definiert ist:

/**
  * Verhindert die Änderung vorhandener Eigenschaftsattribute und -werte und das Hinzufügen neuer Eigenschaften.
  * @param o Objekt, dessen Attribute gesperrt werden sollen.
  */
freeze<T>(o: T): Nur Lesen<T>;

Der Rückgabetyp dieser Methode ist Readonly<T>, ein Zuordnungstyp, der wie folgt definiert ist:

Typ schreibgeschützt<T> = {
  schreibgeschützt [P in Schlüssel von T]: T[P]
};

Diese Syntax kann zunächst entmutigend wirken, also lassen Sie uns sie Schritt für Schritt aufschlüsseln:

  • Ein generischer Readonly wird mit einem Typparameter namens T definiert.
  • Innerhalb der eckigen Klammern wird der Keyof-Operator verwendet. keyof T stellt alle Eigenschaftsnamen vom Typ T als Vereinigung von Zeichenfolgenliteraltypen dar.
  • Das Schlüsselwort „in“ in den eckigen Klammern zeigt an, dass es sich um einen zugeordneten Typ handelt. [P in keyof T]: T[P] bedeutet, den Typ jeder Eigenschaft P vom Typ T in T[P] zu konvertieren. Ohne den schreibgeschützten Modifikator wäre dies eine Identitätsumwandlung.
  • Typ T[P] ist ein Nachschlagetyp, der den Typ einer Eigenschaft P vom Typ T darstellt.
  • Schließlich gibt der Readonly-Modifikator an, dass jede Eigenschaft in eine schreibgeschützte Eigenschaft umgewandelt werden soll.

Da der Typ Readonly<T> generisch ist, wird jeder Typ, den wir für T bereitstellen, korrekt in Object.freeze() gepackt.

const Herkunft = Objekt.freeze({ x: 0, y: 0 });

// Fehler! Kann 'x' nicht zuweisen, da es
// ist eine Konstante oder eine schreibgeschützte Eigenschaft.
Ursprung.x = 42;

Die Syntax des Mapping-Typs ist intuitiver

Dieses Mal verwenden wir den Punkttyp als Beispiel, um grob zu erklären, wie die Typzuordnung funktioniert. Bitte beachten Sie, dass das Folgende nur zu Erklärungszwecken dient und den von TypeScript verwendeten Analysealgorithmus nicht genau wiedergibt.

Beginnen Sie mit dem Typalias:

Typ ReadonlyPoint = Readonly<Punkt>;

Jetzt können wir den Typ Point durch den generischen Typ T in Readonly<T> ersetzen:

Typ ReadonyPoint = {
  schreibgeschützt [P in keyof Point]: Punkt[P]
};

Da wir nun wissen, dass T Point ist, können wir die Vereinigung der durch keyof Point dargestellten Zeichenfolgenliteraltypen bestimmen:

Typ ReadonlyPoint = {
  schreibgeschützt [P in "x" | "y"]: Punkt[p]
};

Typ P stellt jede Eigenschaft x und y dar. Schreiben wir sie als separate Eigenschaften und entfernen die zugeordnete Typsyntax.

Typ ReadonlyPoint = {
  schreibgeschütztes x: Punkt["x"];
  schreibgeschütztes y: Punkt["y"];
};

Schließlich können wir die beiden Nachschlagetypen analysieren und durch die konkreten Typen x und y ersetzen, die beide Zahlen sind.

Typ ReadonlyPoint = {
  schreibgeschützt x: Zahl;
  schreibgeschützt y: Zahl;
};

Schließlich ist der resultierende ReadonlyPoint-Typ derselbe wie der FrozenPoint-Typ, den wir manuell erstellt haben.

Weitere Beispiele für Zuordnungstypen

Den integrierten Typ Readonly<T> haben wir bereits oben in der Datei lib.d.ts gesehen. Darüber hinaus definiert TypeScript andere zugeordnete Typen, die in verschiedenen Situationen nützlich sind. wie folgt:

/**
 * Alle Eigenschaften in T optional machen
 */
Typ Partial<T> = {
  [P in Tonart T]?: T[P]
};

/**
 * Wählen Sie aus T eine Reihe von Eigenschaften K
 */
Typ Pick<T, K erweitert Schlüssel von T> = {
  [P in K]: T[P]
};

/**
 * Konstruieren Sie einen Typ mit einer Reihe von Eigenschaften K vom Typ T
 */
Typ Record<K erweitert Zeichenfolge, T> = {
  [P in K]: T
};

Hier sind zwei weitere Beispiele für Zuordnungstypen. Sie können bei Bedarf auch eigene schreiben:

/**
 * Alle Eigenschaften in T nullwertfähig machen
 */
Typ Nullable<T> = {
  [P in Tonart T]: T[P] | null
};

/**
 * Wandeln Sie alle Eigenschaften von T in Zeichenfolgen um
 */
Typ Stringify<T> = {
  [P in Tonart T]: Zeichenfolge
};

Interessant sind auch Kombinationen aus zugeordneten Typen und Vereinigungen:

Typ X = Nur-Lese<Nullable<Stringify<Punkt>>>;
// Typ X = {
// schreibgeschützt x: Zeichenfolge | null;
// schreibgeschützt y: string | null;
// };

Reale Anwendungsfälle für Mapping-Typen

Mapping-Typen werden in der Praxis häufig verwendet. Schauen wir uns React und Lodash an:

  • Mit der setState-Methode einer react:component können wir den gesamten Status oder eine Teilmenge davon aktualisieren. Wir können so viele Eigenschaften aktualisieren, wie wir wollen, was die Methode setState zu einem großartigen Anwendungsfall für Partial<T> macht.
  • Lodash: Die Pick-Funktion wählt eine Reihe von Eigenschaften aus einem Objekt aus. Diese Methode gibt ein neues Objekt zurück, das nur die von uns ausgewählten Eigenschaften enthält. Dieses Verhalten kann, wie der Name schon sagt, mit Pick<T> erstellt werden.

Bessere Typinferenz für Literale

Zeichenfolgen-, numerische und boolesche Literaltypen (z. B. „abc“, 1 und true) wurden bisher nur abgeleitet, wenn eine explizite Typanmerkung vorhanden war. Ab TypeScript 2.1 wird bei Literaltypen immer davon ausgegangen, dass sie Standardwerte haben. In TypeScript 2.0 wurde das Typsystem um mehrere neue Literaltypen erweitert:

  • Boolescher Literaltyp
  • Numerische Literale
  • Aufzählungsliterale

Der Typ einer Konstantenvariable oder einer schreibgeschützten Eigenschaft ohne Typanmerkung wird als der Typ des Literalinitialisierers abgeleitet. Der Typ einer initialisierten Let-Variable, Var-Variable, eines Parameters oder einer nicht schreibgeschützten Eigenschaft ohne Typanmerkung wird als der erweiterte Literaltyp des Anfangswerts abgeleitet. Der erweiterte Typ eines Zeichenfolgenliterals ist Zeichenfolge, der erweiterte Typ eines Zahlenliterals ist Zahl, der erweiterte Typ eines True- oder False-Literals ist Boolean und der erweiterte Typ eines Aufzählungsliterals ist Aufzählung.

Bessere Inferenz von Konstantenvariablen

Beginnen wir mit lokalen Variablen und dem Schlüsselwort var. Wenn TypeScript die folgende Variablendeklaration sieht, folgert es, dass die Variable baseUrl vom Typ String ist:

var baseUrl = "https://example.com/";
// Abgeleiteter Typ: Zeichenfolge

Dasselbe gilt für Variablen, die mit dem Schlüsselwort let deklariert werden.

let baseUrl = "https://example.com/";
// Abgeleiteter Typ: Zeichenfolge

Es wird davon ausgegangen, dass beide Variablen vom Typ „String“ sind, da sie sich jederzeit ändern können. Sie werden mit einem wörtlichen Zeichenfolgenwert initialisiert, können aber später geändert werden.

Wenn Sie jedoch eine Variable mit dem Schlüsselwort const deklarieren und sie mit einem Zeichenfolgenliteral initialisieren, ist der abgeleitete Typ nicht mehr Zeichenfolge, sondern der Literaltyp:

const baseUrl = "https://example.com/";
// Abgeleiteter Typ: „https://example.com/“

Da sich der Wert einer konstanten Zeichenfolgenvariable nie ändert, ist der abgeleitete Typ spezifischer. Die Variable baseUrl kann keinen anderen Wert als „https://example.com/“ enthalten.

Die wörtliche Typinferenz funktioniert auch für andere primitive Typen. Wenn Sie eine Konstante mit einem direkten numerischen oder booleschen Wert initialisieren, wird der Literaltyp abgeleitet:

const HTTPS_PORT = 443;
// Abgeleiteter Typ: 443

const erinnere mich = true;
// Abgeleiteter Typ: true

Wenn der Initialisierer ein Enumerationswert ist, wird entsprechend der Literaltyp abgeleitet:

Aufzählung FlexDirection {
  Reihe,
  Spalte
}

Konstante Richtung = FlexDirection.Spalte;
// Abgeleiteter Typ: FlexDirection.Column

Beachten Sie, dass der Richtungstyp FlexDirection.Column ist, ein Enumerationsliteraltyp. Wenn Sie das Schlüsselwort let oder var verwenden, um eine Richtungsvariable zu deklarieren, sollte ihr abgeleiteter Typ FlexDirection sein.

Bessere schreibgeschützte Eigenschaftsinferenz

Ähnlich wie bei lokalen Konstantenvariablen wird auch bei schreibgeschützten Eigenschaften mit wörtlichen Initialisierern davon ausgegangen, dass sie einen wörtlichen Typ haben:

Klasse ApiClient {
  private schreibgeschützte Basis-URL = "https://api.example.com/";
  // Abgeleiteter Typ: „https://api.example.com/“

  get(Endpunkt: Zeichenfolge) {
    // ...
  }
}

Schreibgeschützte Klasseneigenschaften können nur direkt oder im Konstruktor initialisiert werden. Der Versuch, den Wert an anderer Stelle zu ändern, führt zu einem Kompilierungsfehler. Daher ist es sinnvoll, den Literaltyp einer schreibgeschützten Klasseneigenschaft abzuleiten, da sich ihr Wert nicht ändert.

Natürlich hat TypeScript keine Ahnung, was zur Laufzeit passiert: Eine mit „readonly“ gekennzeichnete Eigenschaft kann jederzeit durch JavaScript-Code geändert werden. Der Readonly-Modifikator schränkt lediglich den Zugriff auf die Eigenschaft aus TypeScript-Code ein und tut zur Laufzeit nichts. Das heißt, es wird während der Kompilierung gelöscht und erscheint nicht im generierten JS-Code.

Nützlichkeit abgeleiteter Literaltypen

Sie fragen sich vielleicht, warum es sinnvoll ist, Literaltypen für Konstantenvariablen und schreibgeschützte Eigenschaften abzuleiten. Betrachten Sie den folgenden Code:

const HTTP_GET = "GET"; // Abgeleiteter Typ: "GET"
const HTTP_POST = "POST"; // Abgeleiteter Typ: "POST"

Funktion get(URL: Zeichenfolge, Methode: "GET" | "POST") {
  // ...
}

get("https://example.com/", HTTP_GET);

Wenn der abgeleitete Typ der HTTP_GET-Konstante ein String statt „GET“ wäre, würden Sie einen Kompilierungsfehler erhalten, weil Sie HTTP_GET nicht als zweites Argument an die Get-Funktion übergeben können:

Argument vom Typ ‚String‘ kann nicht dem Parameter vom Typ ‚„GET“ | ‚POST‘ zugewiesen werden‘

Natürlich ist die Übergabe beliebiger Zeichenfolgen als Funktionsargumente nicht zulässig, wenn das entsprechende Argument nur zwei bestimmte Zeichenfolgenwerte zulässt. Wenn jedoch für die beiden Konstanten die Literaltypen „GET“ und „POST“ abgeleitet werden, klappt alles.

Oben finden Sie eine ausführliche Erläuterung der TypeScript-Mapping-Typen und der besseren wörtlichen Typinferenz. Weitere Informationen zu TS finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM!

Das könnte Sie auch interessieren:
  • Details zum TypeScript-Mapping-Typ

<<:  Detaillierte Erläuterung der besten Konfiguration für Nginx zur Verbesserung von Sicherheit und Leistung

>>:  So implementieren Sie geplante MySQL-Aufgaben unter Linux

Artikel empfehlen

So öffnen Sie eine Seite in einem Iframe

Lösung: Setzen Sie den Zielattributwert des Links ...

Detaillierte Erläuterung des mobilen Projekts vite2.0 + vue3

1. Technische Punkte Vite-Version vue3 ts Integri...

MySQL 5.7.17 Winx64 Installations- und Konfigurations-Tutorial

Heute habe ich die MySQL-Datenbank erneut auf mei...

Ausführliche Erläuterung der Auswirkungen von NULL auf Indizes in MySQL

Vorwort Ich habe viele Blogs gelesen und von viel...

So installieren Sie Golang unter Linux

Go ist eine Open-Source-Programmiersprache, die d...

Ideen zum Erstellen von Welleneffekten mit CSS

Zuvor habe ich mehrere Möglichkeiten vorgestellt,...

Implementierung der Multisite-Konfiguration von Nginx auf Mac M1

Hinweis: nginx über brew installiert Stammverzeic...

Analyse des neuen Ressourcenmanagementsystems von CocosCreator

Inhaltsverzeichnis 1. Ressourcen und Konstruktion...

Installationstutorial für MySQL 5.1 und 5.7 unter Linux

Das Betriebssystem für die folgenden Inhalte ist:...

Tutorial zur Installation von VMWare15.5 unter Linux

Um VMWare unter Linux zu installieren, müssen Sie...

Vue Router vue-router ausführliche Erklärung Anleitung

Chinesische Dokumentation: https://router.vuejs.o...