- Hallo allerseits, mein Name ist Alexander Nikolaichev. Ich arbeite bei Yandex.Cloud als Front-End-Entwickler und arbeite an der internen Infrastruktur von Yandex. Heute werde ich Ihnen von einer sehr nützlichen Sache erzählen, ohne die es schwierig ist, sich eine moderne Anwendung vorzustellen, insbesondere in großem Maßstab. Dies ist TypeScript, Typisierung, ein engeres Thema - Generika und warum sie benötigt werden.
Beantworten wir zunächst die Frage, warum TypeScript und was die Infrastruktur damit zu tun hat. Unsere Haupteigenschaft der Infrastruktur ist ihre Zuverlässigkeit. Wie kann dies sichergestellt werden? Zunächst können Sie testen.
Wir haben Unit- und Integrationstests. Testen ist eine gute Standardpraxis.
Sie müssen auch die Codeüberprüfung verwenden. Außerdem - Sammlung von Fehlern. Wenn dennoch ein Fehler auftritt, wird er von einem speziellen Mechanismus gesendet, und wir können schnell etwas beheben.
Wie schön wäre es, überhaupt keine Fehler zu machen. Dafür gibt es eine Eingabe, die es uns nicht erlaubt, zur Laufzeit überhaupt einen Fehler zu bekommen. Yandex verwendet den Industriestandard TypeScript. Und da die Anwendungen groß und komplex sind, erhalten wir diese Formel: Wenn wir ein Frontend, eine Typisierung und sogar komplexe Abstraktionen haben, werden wir definitiv zu TypeScript-Generika kommen. Sie können nicht ohne sie auskommen.
Syntax
Um ein grundlegendes Bildungsprogramm durchzuführen, schauen wir uns zunächst die Grundlagen der Syntax an.
Ein Generikum in TypeScript ist ein Typ, der von einem anderen Typ abhängt.
Wir haben einen einfachen Typ, Seite. Wir parametrisieren es mit einem bestimmten Parameter <T>, es wird durch spitze Klammern geschrieben. Und wir sehen, dass es einige Zeichenfolgen und Zahlen gibt, aber <T> ist variabel.
Zusätzlich zu Schnittstellen und Typen können wir dieselbe Syntax auf Funktionen anwenden. Das heißt, derselbe <T> -Parameter wird an das Funktionsargument weitergeleitet, und in der Antwort werden wir dieselbe Schnittstelle wiederverwenden und dort auch übergeben.
Unser generischer Aufruf wird auch durch spitze Klammern mit dem gewünschten Typ geschrieben, genau wie bei der Initialisierung.
Eine ähnliche Syntax gibt es für Klassen. Wir werfen den Parameter in private Felder und haben eine Art Getter. Aber wir schreiben den Typ dort nicht. Warum? Weil TypeScript auf den Typ schließen kann. Dies ist eine sehr nützliche Funktion von ihm, und wir werden sie anwenden.
Mal sehen, was passiert, wenn diese Klasse verwendet wird. Wir erstellen eine Instanz und übergeben anstelle unseres <T> -Parameters eines der Aufzählungselemente. Wir erstellen eine Aufzählung - Russisch, Englisch. TypeScript versteht, dass wir ein Element aus der Aufzählung übergeben haben, und leitet daraus den Typ lang ab.
Aber mal sehen, wie Typinferenz funktioniert. Wenn wir anstelle von Aufzählungselementen eine Konstante aus dieser Aufzählung übergeben, versteht TypeScript, dass dies nicht die gesamte Aufzählung ist, nicht alle ihre Elemente. Und es wird bereits einen bestimmten Wert des Typs geben, nämlich lang en, English.
Wenn wir etwas anderes übergeben, zum Beispiel eine Zeichenfolge, dann scheint es, dass es dieselbe Bedeutung hat wie die Aufzählung. Aber dies ist bereits ein String, ein anderer Typ in TypeScript, und wir werden ihn bekommen. Und wenn wir eine Zeichenfolge als Konstante übergeben, gibt es anstelle einer Zeichenfolge eine Konstante, ein Zeichenfolgenliteral. Dies sind nicht alle Zeichenfolgen. In unserem Fall gibt es eine bestimmte Zeichenfolge en.
Nun wollen wir sehen, wie wir dies erweitern können.
Wir hatten einen Parameter. Nichts hindert uns daran, mehrere Parameter zu verwenden. Alle von ihnen werden durch Kommas getrennt geschrieben. In den gleichen spitzen Klammern, und wir wenden sie der Reihe nach an - von der ersten bis zur dritten. Wir ersetzen die gewünschten Werte, wenn wir aufgerufen werden.
Angenommen, eine Verkettung von numerischen Literalen, ein Standardtyp, eine Verkettung von Zeichenfolgenliteralen. Sie werden alle einfach der Reihe nach niedergeschrieben.
Mal sehen, wie das in Funktionen passiert. Wir erstellen eine Zufallsfunktion. Es gibt zufällig entweder das erste oder das zweite Argument.
Das erste Argument ist vom Typ A, das zweite vom Typ B. Dementsprechend wird ihre Vereinigung zurückgegeben: entweder dies oder das. Zunächst können wir die Funktion explizit eingeben. Wir geben an, dass A eine Zeichenfolge ist, B eine Zahl. TypeScript wird sich ansehen, was wir explizit angegeben haben, und auf den Typ schließen.
Wir können aber auch Typinferenz verwenden. Die Hauptsache ist zu wissen, dass nicht nur der Typ abgeleitet wird, sondern der kleinstmögliche Typ für das Argument.
Angenommen, wir übergeben ein Argument, ein Zeichenfolgenliteral, und es muss Typ A und das zweite Argument, Typ B, entsprechen. Das Minimum, das für ein Zeichenfolgenliteral und eines erforderlich ist, ist Literal A und dasselbe. TypeScript gibt dies an uns aus. Es stellt sich eine solche Verengung der Typen heraus.
Bevor wir zu den folgenden Beispielen übergehen, werden wir sehen, wie Typen im Allgemeinen miteinander in Beziehung stehen, wie diese Beziehungen verwendet werden und wie Ordnung aus dem Chaos aller Typen herausgeholt wird.
Beziehung der Typen
Typen können herkömmlicherweise als eine Art Menge betrachtet werden. Schauen wir uns das Diagramm an, das einen Teil des gesamten Satzes von Typen zeigt.
Wir sehen, dass die Typen darin durch irgendeine Art von Beziehung verbunden sind. Aber welche? Dies sind partielle Ordnungsbeziehungen - was bedeutet, dass ein Typ immer mit seinem Supertyp angegeben wird, dh einem Typ "über" ihm, der alle möglichen Werte abdeckt.
Wenn Sie in die entgegengesetzte Richtung gehen, kann jeder Typ einen Untertyp haben, "weniger" davon.
Was sind die Supertypen einer Zeichenfolge? Alle Verknüpfungen, die eine Zeichenfolge enthalten. Eine Zeichenfolge mit einer Zahl, eine Zeichenfolge mit einem Array von Zahlen, was auch immer. Subtypen sind alle Zeichenfolgenliterale: a, b, c oder ac oder ab.
Es ist jedoch wichtig zu verstehen, dass die Reihenfolge nicht linear ist. Das heißt, nicht alle Typen können verglichen werden. Dies ist logisch und führt zu Typfehlanpassungsfehlern. Das heißt, eine Zeichenfolge kann nicht einfach mit einer Zahl verglichen werden.
Und in dieser Reihenfolge gibt es sozusagen einen Typ, den obersten - unbekannt. Und das niedrigste Analogon der leeren Menge ist niemals. Niemals ist ein Subtyp irgendeines Typs. Und unbekannt ist ein Supertyp jeglicher Art.
Und natürlich gibt es eine Ausnahme - jede. Dies ist ein spezieller Typ, der diese Reihenfolge insgesamt ignoriert und verwendet wird, wenn wir von JavaScript migrieren, um uns nicht um die Typen zu kümmern. Es wird nicht empfohlen, von Grund auf neu zu verwenden. Es lohnt sich, dies zu tun, wenn uns die Position des Typs in dieser Reihenfolge nicht wirklich wichtig ist.
Mal sehen, welche Kenntnisse über diese Reihenfolge uns geben werden.
Wir können Parameter auf ihre Supertypen beschränken. Das Schlüsselwort wird erweitert. Wir werden einen generischen Typ definieren, der nur einen Parameter hat. Wir werden jedoch sagen, dass es sich nur um einen Untertyp der Zeichenfolge oder der Zeichenfolge selbst handeln kann. Wir können keine Nummern übertragen, dies führt zu einem Tippfehler. Wenn wir die Funktion explizit eingeben, können wir in den Parametern nur die Untertypen der Zeichenfolge oder der Zeichenfolge angeben - Apfel und Orange. Beide Zeichenfolgen sind Verkettungen von Zeichenfolgenliteralen. Überprüfung bestanden.
Wir können anhand der Argumente auch automatisch auf Typen schließen. Wenn wir ein String-Literal übergeben haben, ist dies auch ein String. Der Scheck hat funktioniert.
Mal sehen, wie diese Einschränkungen erweitert werden können.
Wir haben uns nur auf eine Zeile beschränkt. Aber ein String ist ein zu einfacher Typ. Ich möchte mit Objektschlüsseln arbeiten. Um mit ihnen zu arbeiten, verstehen wir zunächst, wie die Objektschlüssel selbst und ihre Typen angeordnet sind.
Wir haben ein bestimmtes Objekt. Es gibt eine Art von Feldern: Zeichenfolgen, Zahlen, Boolesche Werte und Schlüssel nach Namen. Um die Schlüssel zu erhalten, verwenden wir das Schlüsselwort keyof. Wir erhalten die Vereinigung aller Schlüsselnamen.
Wenn wir die Werte erhalten möchten, können wir dies über die Syntax in eckigen Klammern tun. Dies ähnelt der JS-Syntax. Es werden nur Typen zurückgegeben. Wenn wir die gesamte Teilmenge der Schlüssel übergeben, erhalten wir die Vereinigung aller Werte dieses Objekts im Allgemeinen.
Wenn wir ein Teil erhalten möchten, können wir dies angeben - nicht alle Schlüssel, sondern eine Teilmenge. Wir erwarten nur die Felder, die den angegebenen Schlüsseln entsprechen. Wenn wir alles auf einen einzigen Fall reduzieren, ist dies ein Feld, und ein Schlüssel gibt einen Wert an. Auf diese Weise erhalten Sie das entsprechende Feld.
Mal sehen, wie man Objektschlüssel verwendet.
Es ist wichtig zu verstehen, dass nach dem Extended-Schlüsselwort ein beliebiger gültiger Typ vorhanden sein kann. Einschließlich aus anderen Generika gebildet oder unter Verwendung von Schlüsselwörtern.
Mal sehen, wie das mit keyof funktioniert. Wir haben den CustomPick-Typ definiert. Tatsächlich ist dies fast eine vollständige Kopie des Pick-Bibliothekstyps aus TypeScript. Was macht er?
Es hat zwei Parameter. Der zweite ist nicht nur ein Parameter. Es müssen die Schlüssel des ersten sein. Wir sehen, dass wir es haben, den Schlüssel von <T> zu erweitern. Daher muss es sich um eine Teilmenge der Schlüssel handeln.
Als nächstes laufen wir für jeden Schlüssel K aus dieser Teilmenge um das Objekt herum, setzen denselben Wert und entfernen speziell die Optionalität abzüglich des Fragezeichens mit der Syntax. Das heißt, alle Felder werden benötigt.
Wir schauen uns die Anwendung an. Wir haben ein Objekt, darin die Namen der Felder. Wir können nur eine Teilmenge davon nehmen - a, b oder c oder alle auf einmal. Wir haben a oder c genommen. Es werden nur die entsprechenden Werte angezeigt, aber wir sehen, dass das Feld a erforderlich geworden ist, da wir das Fragezeichen relativ gesehen entfernt haben. Wir haben diesen Typ definiert und verwendet. Niemand stört uns, dieses Generikum zu nehmen und es in ein anderes Generikum zu schieben.
Wie kommt es dazu? Wir haben einen anderen Typ definiert, Custom. Der zweite Parameter erweitert nicht keyof, sondern das Ergebnis der Anwendung des Generikums, das wir rechts gezeigt haben. Wie funktioniert es, was übertragen wir überhaupt darauf?
Wir übergeben jedes Objekt und alle seine Schlüssel an dieses Generikum. Dies bedeutet, dass die Ausgabe eine Kopie des Objekts mit allen erforderlichen Feldern ist. Diese Kette des Verschachtelns eines Generikums in ein anderes Generikum usw. kann abhängig von den Aufgaben unbegrenzt fortgesetzt und der Code strukturiert werden. Führen Sie wiederverwendbare Konstrukte in Generika usw. ein.
Die angegebenen Argumente müssen nicht in Ordnung sein. Ähnlich wie der P-Parameter erweitert er die T-Tasten im CustomPick-Generikum. Aber niemand hat uns gestört, es als ersten Parameter und T als zweiten anzugeben. TypeScript geht nicht sequentiell über Parameter hinweg. Er sieht sich alle Parameter an, die wir angegeben haben. Dann löst er ein bestimmtes Gleichungssystem, und wenn er eine Lösung für die Typen findet, die dieses System erfüllen, hat die Typprüfung bestanden.
In dieser Hinsicht können Sie solch ein lustiges Generikum ableiten, bei dem die Parameter die Schlüssel des anderen erweitern: a - das sind die Schlüssel b, b - die Schlüssel a. Es scheint, wie kann das sein, die Schlüssel der Schlüssel? Wir wissen jedoch, dass TypeScript-Zeichenfolgen tatsächlich JavaScript-Zeichenfolgen sind und JavaScript-Zeichenfolgen ihre eigenen Methoden haben. Dementsprechend reicht jeder Name der Zeichenfolgenmethode aus. Weil der Name einer String-Methode auch ein String ist. Und von dort hat sie ihren Namen.
Dementsprechend können wir eine solche Einschränkung erhalten, und das Gleichungssystem wird aufgelöst, wenn wir die erforderlichen Typen angeben.
Mal sehen, wie dies in der Realität genutzt werden kann. Wir verwenden es für die API. Es gibt eine Site, auf der Yandex-Anwendungen bereitgestellt werden. Wir möchten das Projekt und den dazugehörigen Service anzeigen.
In diesem Beispiel habe ich ein Projekt zum Ausführen von virtuellen qyp-Maschinen für Entwickler erstellt. Wir wissen, dass wir die Struktur dieses Objekts im Backend haben, wir nehmen es von der Basis. Neben dem Projekt gibt es aber noch andere Objekte: Entwürfe, Ressourcen. Und alle haben ihre eigenen Strukturen.
Darüber hinaus möchten wir nicht das gesamte Objekt anfordern, sondern einige Felder - den Namen und den Namen des Dienstes. Es gibt eine solche Möglichkeit, das Backend ermöglicht es Ihnen, Pfade zu übergeben und eine unvollständige Struktur zu erhalten. DeepPartial wird hier beschrieben. Wir werden etwas später lernen, wie man es entwirft. Dies bedeutet jedoch, dass nicht das gesamte Objekt übertragen wird, sondern ein Teil davon.
Wir möchten eine Funktion schreiben, die diese Objekte anfordert. Schreiben wir in JS. Aber wenn Sie genau hinschauen, können Sie Tippfehler sehen. In der Art von "Projekt" gibt es in den Pfaden auch einen Tippfehler im Dienst. Nicht gut, der Fehler wird zur Laufzeit sein.
Die TS-Variante scheint sich abgesehen von den Pfaden nicht wesentlich zu unterscheiden. Wir werden jedoch zeigen, dass es im Feld Typ keine anderen Werte geben kann als die, die wir im Backend haben.
Das Pfadfeld hat eine spezielle Syntax, mit der wir einfach keine anderen fehlenden Felder auswählen können. Wir verwenden eine Funktion, bei der wir einfach die Verschachtelungsebenen auflisten, die wir benötigen, und ein Objekt erhalten. In der Tat ist es ein Anliegen unserer Implementierung, Pfade von dieser Funktion zu erhalten. Hier gibt es kein Geheimnis, sie benutzt einen Proxy. Das ist uns nicht so wichtig.
Mal sehen, wie man die Funktion bekommt.
Wir haben eine Funktion, ihre Verwendung. Es gibt diese Struktur. Zuerst wollen wir alle Namen bekommen. Wir schreiben einen Typ, bei dem der Name mit der Struktur übereinstimmt.
Nehmen wir an, wir beschreiben für ein Projekt irgendwo seinen Typ. In unserem Projekt generieren wir Taipings aus Protobuf-Dateien, die im allgemeinen Repository verfügbar sind. Als nächstes sehen wir, dass wir alle verwendeten Typen haben: Projekt, Entwurf, Ressource.
Schauen wir uns die Implementierung an. Schauen wir es uns der Reihe nach an.
Es gibt eine Funktion. Lassen Sie uns zunächst sehen, wie es parametrisiert wird. Nur durch diese zuvor beschriebenen Namen. Mal sehen, was es zurückgibt. Es werden Werte zurückgegeben. Warum ist das so? Wir haben die Syntax in eckigen Klammern verwendet. Da wir jedoch eine Zeichenfolge an den Typ übergeben, ist die Verkettung von Zeichenfolgenliteralen bei Verwendung immer eine Zeichenfolge. Es ist nicht möglich, eine Zeichenfolge zu erstellen, die gleichzeitig ein Projekt und eine Ressource ist. Sie ist immer eine und die Bedeutung ist auch die gleiche.
Lassen Sie uns alles in DeepPartial einwickeln. Optionaler Typ, optionale Struktur. Das Interessanteste sind die Parameter. Wir fragen sie mit Hilfe eines anderen Generikums.
Der Typ, mit dem die generischen Parameter parametrisiert werden, entspricht auch der Einschränkung der Funktion. Es kann nur den Namenstyp akzeptieren - Projekt, Ressource, Entwurf. ID ist natürlich eine Zeichenfolge, die uns nicht interessiert. Hier ist der Typ, den wir angegeben haben, einer von drei. Ich frage mich, wie die Pfadfunktion funktioniert. Dies ist ein weiteres Generikum - warum verwenden wir es nicht wieder? Alles, was es tut, ist einfach eine Funktion zu erstellen, die ein Array von jedem zurückgibt, da unser Objekt Felder von jedem Typ haben kann, wir wissen nicht, welche. In dieser Implementierung erhalten wir die Kontrolle über die Typen.
Wenn es jemand einfach fand, gehen wir zu den Kontrollstrukturen über.
Kontrollkonstrukte
Wir werden nur zwei Konstruktionen betrachten, aber sie werden ausreichen, um fast alle Aufgaben abzudecken, die wir brauchen.
Was sind bedingte Typen? Sie sind Ternarks in JavaScript sehr ähnlich, nur für Typen. Wir haben eine Bedingung, dass Typ a ein Subtyp von b ist. Wenn ja, geben Sie c zurück. Wenn nicht, geben Sie d zurück. Das heißt, dies ist normal, wenn auch nur für Typen.
Mal sehen, wie es funktioniert. Wir werden einen CustomExclude-Typ definieren, der im Wesentlichen die Bibliothek Exclude kopiert. Es wirft nur die Elemente aus der Typunion heraus, die wir brauchen. Wenn a ein Subtyp von b ist, geben Sie leer zurück, andernfalls geben Sie a zurück. Dies ist seltsam, wenn Sie sich ansehen, warum es mit Joins funktioniert.
Ein spezielles Gesetz ist praktisch: Wenn es eine Vereinigung gibt und wir die Bedingungen mit Extend prüfen, prüfen wir jedes Element einzeln und kombinieren sie dann erneut. Dies ist ein solches transitives Gesetz, nur für bedingte Typen.
Wenn wir CustomExclude verwenden, betrachten wir nacheinander jedes Beobachtungselement. a erweitert a, a ist ein Subtyp, gibt aber void zurück; b ist ein Subtyp von a? Nein - Rückkehr b. c ist auch kein Subtyp von a, return c. Dann kombinieren wir, was übrig bleibt, alle Pluszeichen, wir bekommen b und c. Wir warfen ein und bekamen, was wir wollten.
Die gleiche Technik kann verwendet werden, um alle Schlüssel eines Tupels zu erhalten. Wir wissen, dass ein Tupel dasselbe Array ist. Das heißt, es hat JS-Methoden, aber wir brauchen diese nicht, wir brauchen nur Indizes. Dementsprechend werfen wir einfach die Namen aller Methoden aus allen Schlüsseln des Tupels heraus und erhalten nur die Indizes.
Wie definieren wir unseren zuvor erwähnten DeepPartial-Typ? Hier wird zum ersten Mal die Rekursion verwendet. Wir gehen alle Schlüssel des Objekts durch und schauen. Ist der Wert ein Objekt? Wenn ja, wenden Sie es rekursiv an. Wenn nicht, und dies ist eine Zeichenfolge oder eine Zahl, lassen Sie sie und machen Sie alle Felder optional. Es ist immer noch ein Teiltyp.
Dieser rekursive Aufruf und die bedingten Typen vervollständigen TypeScript Turing tatsächlich. Aber beeilen Sie sich nicht, sich darüber zu freuen. Es macht dich wütend, wenn du versuchst, so etwas zu tun, eine Abstraktion mit viel Rekursivität.
TypeScript überwacht dies und gibt selbst auf der Ebene seines Compilers einen Fehler aus. Sie werden nicht einmal warten, bis dort etwas gezählt wird. Und für solch einfache Fälle, in denen wir nur einen Anruf haben, ist die Rekursion durchaus geeignet.
Mal sehen, wie es funktioniert. Wir wollen das Problem des Patchen des Objektfeldes lösen. Wir verwenden eine virtuelle Cloud, um die Einführung von Anwendungen zu planen, und wir benötigen Ressourcen.
Nehmen wir an, wir haben CPU-Ressourcen und Kerne verwendet. Jeder braucht Kernel. Ich habe das Beispiel vereinfacht, und es gibt nur Ressourcen, nur Kernel, und es sind Zahlen.
Wir wollen eine Funktion machen, die sie patcht, Werte patcht. Kernel hinzufügen oder subtrahieren. In demselben JavaScript gibt es, wie Sie vielleicht vermutet haben, Tippfehler. Hier fügen wir einer Zeichenfolge eine Zahl hinzu - nicht sehr gut.
In TypeScript hat sich fast nichts geändert, aber tatsächlich sagt Ihnen dieses Steuerelement auf IDE-Ebene, dass Sie nichts anderes als diese Zeichenfolge oder eine bestimmte Nummer übergeben können.
Mal sehen, wie das geht. Wir müssen eine solche Funktion bekommen, und wir wissen, dass wir ein Objekt dieser Art haben. Sie müssen verstehen, dass wir nur die Nummer und die Felder patchen. Das heißt, Sie müssen nur den Namen der Felder abrufen, in denen Zahlen vorhanden sind. Wir haben nur ein Feld und es ist eine Zahl.
Mal sehen, wie dies in TypeScript implementiert wird.
Wir haben eine Funktion definiert. Es hat nur drei Argumente, das Objekt, das wir patchen, und den Feldnamen. Dies ist jedoch nicht nur ein Feldname. Es kann nur der Name von numerischen Feldern sein. Wir werden jetzt herausfinden, wie dies gemacht wird. Und der Patcher selbst, der eine reine Funktion ist.
Es gibt eine bestimmte unpersönliche Funktion, einen Patch. Wir sind nicht an seiner Implementierung interessiert, sondern daran, wie man einen so interessanten Typ erhält, um die Schlüssel nicht nur numerischer, sondern beliebiger Felder nach Bedingung zu erhalten. Wir haben hier Zahlen.
Lassen Sie uns analysieren, wie dies geschieht.
Wir gehen alle Schlüssel des übergebenen Objekts durch und führen dann diese Prozedur aus. Lassen Sie uns sehen, dass das Objektfeld ein Subtyp des gewünschten ist, dh ein numerisches Feld. Wenn ja, dann ist es wichtig, dass wir nicht den Feldwert, sondern den Feldnamen schreiben, sonst im Allgemeinen nichts, niemals.
Aber dann stellte sich so ein seltsames Objekt heraus. Alle numerischen Felder hatten ihre Namen als Werte und alle nicht numerischen Felder wurden leer. Dann nehmen wir alle Werte dieses seltsamen Objekts.
Da jedoch alle Werte Leere enthalten und die Leere in Kombination zusammenbricht, bleiben nur die Felder übrig, die numerischen entsprechen. Das heißt, wir haben nur die erforderlichen Felder.
Das Beispiel zeigt: Es gibt ein einfaches Objekt, das Feld ist eines. Ist das eine Nummer? Ja. Das Feld ist eine Zahl, ist es eine Zahl? Ja. Die letzte Zeile ist keine Zahl. Wir erhalten nur die notwendigen numerischen Felder.
Damit ist das erledigt. Ich habe den schwierigsten zum Schluss verlassen. Dies ist die Typinferenz - Infer. Erfassen eines Typs in einem bedingten Konstrukt.
Es ist untrennbar mit dem vorherigen Thema verbunden, da es nur mit einem bedingten Konstrukt funktioniert.
Wie sieht es aus? Angenommen, wir möchten die Elemente eines Arrays kennen. Eine bestimmte Art von Array kam, wir möchten ein bestimmtes Element kennen. Wir schauen: Wir haben eine Art Array erhalten. Es ist ein Subtyp des Arrays aus der Variablen x. Wenn ja, geben Sie dieses x, Array-Element zurück. Wenn nicht, geben Sie die Leere zurück.
In diesem Zustand wird der zweite Zweig niemals ausgeführt, da wir den Typ mit einem beliebigen Array parametrisiert haben. Natürlich wird es ein Array von etwas sein, weil ein Array von irgendetwas nur Elemente haben kann.
Wenn wir ein Array von Zeichenfolgen übergeben, wird erwartet, dass eine Zeichenfolge an uns zurückgegeben wird. Und es ist wichtig zu verstehen, dass hier nicht nur ein Typ definiert ist. Aus dem String-Array geht visuell hervor: Es gibt Strings. Aber mit einem Tupel ist nicht alles so einfach. Für uns ist es wichtig zu wissen, dass der kleinstmögliche Supertyp bestimmt wird. Es ist klar, dass alle Arrays sozusagen Subtypen eines Arrays mit einem oder mit unbekanntem sind. Dieses Wissen gibt uns nichts. Für uns ist es wichtig, das Minimum zu kennen.
Nehmen wir an, wir geben ein Tupel ab. Tatsächlich sind Tupel auch Arrays, aber wie können wir feststellen, welche Elemente dieses Array enthält? Wenn es ein String-Tupel einer Zahl gibt, handelt es sich tatsächlich um ein Array. Das Element muss jedoch vom gleichen Typ sein. Und wenn es sowohl eine Zeichenfolge als auch eine Zahl gibt, gibt es eine Vereinigung.
TypeScript gibt dies aus und wir erhalten genau die Verkettung einer Zeichenfolge und einer Zahl für ein solches Beispiel.
Sie können nicht nur die Erfassung an einem Ort verwenden, sondern auch so viele Variablen, wie Sie möchten. Angenommen, wir definieren einen Typ, der einfach die Elemente des Tupels vertauscht: den ersten mit dem zweiten. Wir greifen das erste Element, das zweite und tauschen sie aus.
Tatsächlich wird jedoch nicht empfohlen, zu viel damit zu flirten. Normalerweise reicht für 90% der Aufgaben nur eine Art der Erfassung aus.
Sehen wir uns ein Beispiel an. Ziel: Sie müssen je nach Status der Anforderung entweder eine gute oder eine schlechte Option anzeigen. Hier finden Sie Screenshots von unserem Anwendungsbereitstellungsservice. Eine Entität, ReplicaSet. Wenn die Anforderung vom Backend einen Fehler zurückgegeben hat, müssen Sie ihn rendern. Gleichzeitig gibt es eine API für das Backend. Mal sehen, was Infer damit zu tun hat.
Wir wissen, dass wir erstens Redux und zweitens Redux Thunk verwenden. Und wir müssen den Bibliotheks-Thunk transformieren, um dies tun zu können. Wir haben einen schlechten und einen guten Weg.
Und wir wissen, dass ein guter Weg für extraReducers im Redux-Toolkit so aussieht. Wir wissen, dass es PayLoad gibt, und wir möchten die benutzerdefinierten Typen, die zu uns kamen, aus dem Backend herausholen, aber nicht nur, sondern auch Informationen über eine gute oder schlechte Anfrage: Gibt es einen Fehler oder nicht? Wir brauchen ein Generikum für diese Ausgabe.
Ich mache keinen Vergleich über JavaScript, weil es keinen Sinn ergibt. In JavaScript können Sie Typen im Prinzip in keiner Weise steuern und sich nur auf den Speicher verlassen. Hier gibt es keine schlechte Option, weil es einfach keine gibt.
Wir wissen, dass wir diesen Typ wollen. Aber wir haben nicht nur eine Aktion. Wir müssen den Versand mit dieser Aktion anrufen. Und wir brauchen diese Ansicht, in der wir einen Fehler durch den Anforderungsschlüssel anzeigen müssen. Das heißt, Sie müssen solche zusätzlichen Funktionen mithilfe der withRequestKey-Methode zusätzlich zu Redux Thunk mischen.
Wir haben natürlich diese Methode, aber wir haben auch die ursprüngliche API-Methode, getReplicaSet. Es ist irgendwo geschrieben und wir müssen den Redux-Thunk mit einer Art Adapter überschreiben. Mal sehen, wie es geht.
Wir brauchen eine solche Funktion. Es ist ein Trottel mit so viel zusätzlicher Funktionalität. Es klingt beängstigend, aber seien Sie nicht beunruhigt, jetzt werden wir es in den Regalen zerlegen, damit es klar ist.
Es gibt einen Adapter, der den ursprünglichen Bibliothekstyp erweitert. Wir mischen einfach eine zusätzliche withRequestKey-Methode und einen benutzerdefinierten Aufruf dieses Bibliothekstyps ein. Mal sehen, was das Hauptmerkmal des Generikums ist, welche Parameter verwendet werden.
Das erste ist nur unsere API, ein Objekt mit Methoden. Wir können getReplicaSet ausführen, Projekte und Ressourcen abrufen, es spielt keine Rolle. Wir verwenden eine bestimmte Methode in der aktuellen Methode, und der zweite Parameter ist nur der Name der Methode. Als Nächstes verwenden wir die Parameter der angeforderten Funktion. Wir verwenden den Bibliothekstyp "Parameter". Dies ist ein TypeScript-Typ. In ähnlicher Weise verwenden wir den Bibliothekstyp ReturnType für die Backend-Antwort. Dies ist für das, was die Funktion zurückgegeben hat.
Dann übergeben wir einfach unsere benutzerdefinierte Ausgabe an den AsyncThunk-Typ, den uns die Bibliothek zur Verfügung gestellt hat. Aber was ist diese Schlussfolgerung? Dies ist ein weiteres Generikum. In der Tat sieht es einfach aus. Wir speichern nicht nur die Antwort vom Server, sondern auch unsere Parameter, die wir übergeben haben. Nur um sie in Reducer im Auge zu behalten. Als nächstes schauen wir uns withRequestKey an. Unsere Methode fügt nur einen Schlüssel hinzu. Was gibt er zurück? Der gleiche Adapter, weil wir ihn wiederverwenden können. Wir müssen überhaupt nicht mit RequestKey schreiben. Dies ist nur eine zusätzliche Funktionalität. Es wird verpackt und gibt rekursiv denselben Adapter an uns zurück, und wir übergeben dort dasselbe.
Lassen Sie uns abschließend sehen, wie Sie an Reducer ausgeben, was dieser Thunk an uns zurückgegeben hat.
Wir haben diesen Adapter. Die Hauptsache ist, sich daran zu erinnern, dass es vier Parameter gibt: API, API-Methode, Parameter (Eingabe) und Ausgabe. Wir müssen einen Ausweg finden. Wir erinnern uns jedoch, dass wir eine benutzerdefinierte Ausgabe haben: sowohl die Serverantwort als auch den Anforderungsparameter.
Wie kann ich das mit Infer machen? Wir sehen, dass dieser Adapter an den Eingang geliefert wird, aber es ist im Allgemeinen jeder: jeder, jeder, jeder, jeder. Wir müssen diesen Typ zurückgeben, es sieht so aus, die Serverantwort und die Anforderungsparameter. Und wir schauen uns an, wo der Eingang sein soll. Auf dem Dritten. Hier platzieren wir unsere Typerfassung. Wir bekommen den Eingang. Ebenso ist der Ausgang an vierter Stelle.
TypeScript basiert auf strukturierter Typisierung. Er zerlegt diese Struktur und versteht, dass der Eingang hier an dritter Stelle und der Ausgang an vierter Stelle ist. Und wir geben die gewünschten Typen zurück.
Damit wir eine Typinferenz erreicht haben, haben wir bereits im Reducer selbst Zugriff darauf. In JavaScript ist dies grundsätzlich nicht möglich.