TypeScript strenger machen. Yandex-Bericht

Wie kann man TypeScript zu einem strengen, aber fairen Begleiter machen, der Sie vor bösen Fehlern schützt und Ihnen mehr Vertrauen in Ihren Code gibt? Alexey Veselovsky veselovskiyaiberücksichtigte mehrere Merkmale der TS-Konfiguration, die die Augen vor unverzeihlichen Freiheiten verschließen. Der Bericht beschreibt die Dinge, die am besten vermieden werden und mit denen Sie äußerst vorsichtig sein müssen. Sie werden etwas über die wunderbare io-ts-Bibliothek lernen - sie ermöglicht es Ihnen, Daten, die in den Code eingehen und Fehler an perfekt geschriebenen Stellen verursachen können, leicht zu erkennen und sogar zu verhindern.



- Hallo allerseits, mein Name ist Lesha, ich bin ein Frontend-Entwickler. Lasst uns beginnen. Ich werde Ihnen ein wenig über mich und das Projekt erzählen, in dem ich arbeite. Flow lernt Englisch von Yandex.Practicum. Die Veröffentlichung fand im April dieses Jahres statt. Die Vorderseite wurde direkt in TypeScript geschrieben, vorher gab es keinen Code.







Ein wenig über meine Erfahrung. In einem fernen Jahr begann ich zu programmieren. 2013 begann er zu arbeiten.







Fast sofort wurde mir klar, dass ich mich viel mehr für die Front interessierte, aber ich hatte Erfahrung mit statisch typisierten Sprachen. Ich habe angefangen, JavaScript zu verwenden, und diese statische Eingabe war nicht vorhanden. Es schien mir bequem, ich mochte es.



Bei einem Projektwechsel musste ich mit TypeScript arbeiten. Ich erzähle Ihnen von den Profis, die ich durch den Wechsel zu TypeScript realisiert habe. Einfacheres Verständnis des Projekts. Wir haben eine Beschreibung der im Projekt verwendeten Datentypen und Konvertierungen zwischen ihnen.







Es ist sicherer, Änderungen am Code vorzunehmen: Bei Änderungen am Backend oder nur an einem Teil des Codes hebt TypeScript die Stellen hervor, an denen Fehler aufgetreten sind.



Es gibt weniger Bedenken hinsichtlich der Typen. Wenn wir neue Funktionen erstellen, legen wir sofort die Typen fest, mit denen die Funktionen arbeiten, und wir können uns weniger Sorgen machen, dass wir unterschiedliche Daten erhalten.



Es besteht keine Angst, dass null oder undefiniert kommen wird, wir müssen nicht paranoid sein, unnötige if- und ähnliche Konstrukte einfügen.







Anfang dieses Jahres bin ich zu Flow gezogen. Hier wird auch TypeScript verwendet, aber ich habe es nicht ein bisschen erkannt. Warum? Er war zu nett zu mir, ein Viertel der Kundenfehler bezog sich auf null und undefiniert. Ich begann herauszufinden, was los war, und fand eine Zeile in der Konfiguration, die das gesamte Verhalten von TypeScript veränderte.







Dies ist die Einbeziehung von strict. Es war nicht vorhanden, musste jedoch aktiviert werden, um die Überprüfung zu verbessern.



TypeScript: streng



Was ist streng? Woraus besteht es?







Dies ist eine Reihe von Flags, die einzeln aktiviert werden können, aber meiner Meinung nach sind sie alle sehr nützlich. noImplicitAny - Bevor wir dieses Flag aktivieren, können wir beispielsweise Funktionen deklarieren, deren Parameter implizit sind, wie z. Wenn wir dieses Flag aktivieren, müssen wir die Eingabe an Stellen hinzufügen, an denen TypeScript den Typ nicht aus dem Kontext berechnen kann.



Das heißt, im zweiten Fall müssen wir die Eingabe hinzufügen, da es keinen Kontext als solchen gibt. Im dritten Fall, in dem wir eine Karte haben, können wir keine Eingabe für a hinzufügen, da aus dem Kontext hervorgeht, dass es einen Zahlentyp geben wird.







noImplicitThis. TypeScript verpflichtet uns, dies einzugeben, wenn kein Kontext vorhanden ist. Wenn der Kontext ein Objekt oder eine Klasse ist, müssen wir dies nicht tun.







alwaysStrict. Fügt jeder Datei "use strict" hinzu. Es hat jedoch Auswirkungen darauf, wie JavaScript unseren Code ausführt. (...)







strictBindCallApply. Aus irgendeinem Grund überprüft TypeScript vor dem Aktivieren dieser Option nicht das Binden, Anwenden und Aufrufen von Typen. Nachdem er es eingeschaltet hat, überprüft er sie und erlaubt uns nicht, solche bösen Dinge zu tun.







strictNullChecks ist meiner Meinung nach die am meisten benötigte Prüfung. Es verpflichtet uns, bei der Eingabe die Stellen anzugeben, an denen null oder undefiniert kommen können. Vor der Aufnahme können wir null oder undefiniert übergeben, wenn dies nicht explizit angegeben ist, und dementsprechend einen Fehler erhalten. Danach ist die Kontrolle viel besser.







Als nächstes strictFunctionTypes. Die Situation hier ist etwas komplizierter. Stellen wir uns vor, wir haben drei Funktionen. Einer arbeitet mit Tieren, einer mit Hunden und einer mit Katzen. Ein Hund und eine Katze sind Tiere. Das heißt, es ist falsch, mit einem Hund genauso zu arbeiten wie mit einer Katze, weil sie unterschiedlich sind. Es funktioniert korrekt mit einem Hund wie mit einem Tier.



Die dritte Option ist, wenn wir versuchen, mit einem Tier wie einem Hund zu arbeiten. Aus irgendeinem Grund ist dies in TypeScript zunächst zulässig. Wenn Sie diese Option aktivieren, ist sie jedoch ungültig und es werden bestimmte Überprüfungen durchgeführt.







Als nächstes strictPropertyInitialization. Dies ist für Klassen. Es verpflichtet uns, Anfangswerte entweder beim Deklarieren einer Eigenschaft oder in einem Konstruktor festzulegen. Manchmal gibt es Zeiten, in denen Sie diese Regel umgehen müssen. Sie können ein Ausrufezeichen verwenden, aber auch dies verpflichtet uns, etwas vorsichtiger zu sein.



Also habe ich herausgefunden, dass wir strikt aktivieren müssen. Ich versuche es einzuschalten und es tauchen viele Fehler auf. Daher wurde beschlossen, eine Übergangskonfiguration zu streng zu verwenden. Wir setzen streng in drei Schritten.







Die erste Stufe: Wir fügen "strict" hinzu: true to tsconfig, und dementsprechend fordert uns unsere Entwicklungsumgebung zu Orten mit einem Fehler auf, der durch die Einbeziehung von strict verursacht wird.



Für das Webpack erstellen wir jedoch eine spezielle tsconfig, die streng falsch ist, und verwenden diese beim Erstellen. Das heißt, während der Montage bricht nichts, aber in unserem Editor sehen wir diese Fehler. Und wir können sie sofort reparieren. Dann wenden wir uns von Zeit zu Zeit der zweiten Stufe zu, dies ist eine Lösung. Wir bauen unser Projekt mit der üblichen tsconfig. Wir korrigieren einige der aufgetretenen Fehler und wiederholen dies alles in unserer Freizeit.



Durch solche Aktionen haben wir die Anzahl unserer Fehler bisher von 400 auf 200 reduziert. Wir freuen uns darauf, mit der dritten Stufe fortzufahren - dem Entfernen von webpackTsConfig und der Verwendung von tsconfig beim Erstellen, jedoch mit strikter Aktivierung.



TypeScript:



Sie können ein wenig über die kleinen Feinheiten von TypeScript sprechen, die nicht streng sind, aber schwer korrekt zu formalisieren sind.







Beginnen wir mit dem Ausrufezeichenoperator. Was können Sie damit tun? In diesem Fall beziehen Sie sich auf ein Feld, das undefiniert sein kann, als ob es nicht undefiniert sein kann. Im strengen Modus ist es sinnvoll, wenn wir versuchen, auf ein Feld zuzugreifen, indem wir explizit sagen: Ich bin sicher, dass es definitiv nicht null oder undefiniert ist. Aber das ist schlecht, denn wenn es sich plötzlich als null oder undefiniert herausstellt, erhalten wir zur Laufzeit natürlich einen Fehler.



ESLint wird uns helfen, solche Dinge zu vermeiden, es wird uns einfach verbieten. Wir haben es geschafft. Wie behebe ich das vorherige Beispiel jetzt?



Angenommen, wir haben eine solche Situation.







Es gibt ein Element, es kann vom Typ Link oder Span sein. Unter dem Kopf verstehen wir, dass span nur Text ist und Link Text und Link ist.



(Bild)



Wir haben jedoch vergessen, die TypeScript-Sprache anzugeben, sodass in der Funktion getItemHtml die Situation auftritt, dass wir im Fall eines Links sagen müssen: href ist nicht optional, wird es definitiv sein. Dies ist auch ein potenzieller Fehlerort. Wie man es repariert?







Die erste Option besteht darin, die Eingabe zu korrigieren, dh TypeScript explizit anzuzeigen, dass für einen Link eine href und für span eine Option erforderlich ist.







Und das Ausrufezeichen wird hier nicht benötigt.







Zweite Korrekturoption. Angenommen, der Artikeltyp wird von uns nicht beschrieben und wir können ihn nicht einfach annehmen und einschränken. Dann können wir es auf ähnliche Weise umschreiben.







Bitte beachten Sie: Der Scheck ist gerade erschienen. Als nächstes folgt die Protokollierung, dass der Programmierer diesen Wert beim Schreiben dieses Codes nicht erwartet hat. In Zukunft werden wir diesen Fehler sehen und geeignete Maßnahmen ergreifen.



Als nächstes versuchen wir, unseren Gegenstand irgendwie zu rendern. Hier können Sie dem Benutzer einfach einen Fehler geben. Wenn es sich jedoch um unbedeutende Daten handelt, können Sie einen Stub wie hier erstellen.



wie





Des Weiteren. Es gibt auch einen als Operator. Was können Sie damit tun?







Es erlaubt Ihnen zu sagen - ich weiß besser, es gibt so und so einen Typ - und sich auch zu einem Fehler zu führen.



Arrays



Die Methoden des Kampfes sind die gleichen. Was Sie etwas vorsichtiger tun müssen, sind Arrays. TypeScript ist kein Allheilmittel, es werden einige Punkte nicht überprüft. Zum Beispiel können wir auf ein nicht existierendes Array-Element verweisen. In diesem Fall nehmen wir das erste Element des Arrays und erhalten einen Fehler in diesem Code. Wie können wir das beheben?







Auch hier gibt es zwei Möglichkeiten. Der erste Weg ist das Tippen. Wir sagen, dass wir das erste Element haben und beziehen uns furchtlos auf dieses Element. Oder wir werden prüfen, wir werden protokollieren, wenn plötzlich etwas nicht stimmt, wenn wir explizit ein nicht leeres Array erwarten.



Objekte



Bei Objekten ist es genauso. Wir können ein Objekt deklarieren, das eine beliebige Anzahl von Eigenschaften haben kann, und auch einen undefinierten Fehler erhalten.







Auch hier können Sie explizite Anweisungen geben, welche Eigenschaften erforderlich sind, oder einfach überprüfen.



irgendein



Jetzt ist das Offensichtliche.







Sie können auf jede Eigenschaft eines Objekts verweisen, als ob überhaupt keine Eingabe erfolgt wäre. In diesem Fall können wir mit x machen, was wir wollen. Und wieder schießen Sie sich in den Fuß, machen Sie Fehler.



Auch hier ist es besser, dies mit ESLint explizit zu verbieten. Aber es gibt Situationen, in denen es von selbst erscheint.







In diesem Fall liefert JSON.parse beispielsweise nur diesen Typ any. Was kann getan werden?







Sie können einfach sagen: Ich glaube Ihnen nicht, sagen wir besser, ich weiß nicht, was es ist, und ich werde damit weiterleben. Wie kann man damit leben? Hier ist ein hypothetisches Beispiel.







Es gibt einen Benutzer, der Benutzer hat einen erforderlichen Namen und eine optionale E-Mail.







Wir schreiben die parseUser-Funktion. Es nimmt eine JSON-Zeichenfolge und gibt unser Objekt an uns zurück. Jetzt fangen wir an, das alles zu überprüfen. Zuerst sehen wir die Zeile mit Analyse und Unbekanntem, die uns von der vorherigen Folie bekannt ist. Als nächstes beginnen wir zu überprüfen.







Wenn es kein Objekt ist oder null ist, werfen Sie einen Fehler.







Wenn keine erforderliche Namenseigenschaft oder keine Zeichenfolge vorhanden ist, wird ein Fehler ausgegeben. Hier ist die Fortsetzung des Codes.







Wir beginnen Benutzer zu bilden, da alle erforderlichen Felder bereits gesammelt wurden.







Als nächstes prüfen wir, ob ein E-Mail-Feld vorhanden ist. Wenn dies der Fall ist, überprüfen wir den Typ und geben einen Fehler aus, wenn der Typ nicht übereinstimmt. Wenn es keine E-Mail gibt, senden wir nichts und geben das Ergebnis zurück. Alles in Ordnung ist. Aber Sie müssen viel für den einfachsten Typ schreiben.







Und es braucht viele Kontrollen



Wir brauchen viel Validierung, da eine typische JSON-Anfrage so aussieht.







Ohne weiteres ist dies nur fetch und json (). Die Konvertierung von any nach SomeRequestResponse wird im Gegenzug angezeigt. Dies muss auch bekämpft werden. Sie können es auf die vorherige Weise tun, oder Sie können es ein wenig anders machen.



io-ts



Unter der Haube ist es dasselbe: Wir verwenden eine spezielle Bibliothek zur Typprüfung. In diesem Fall ist es io-ts. Hier ist ein einfaches Beispiel, wie man damit arbeitet.







Nehmen wir den vorherigen Benutzertyp und schreiben ihn in die von uns verwendete Bibliothek. Ja, das Tippen ist hier etwas komplizierter, aber zwei Bedingungen müssen gleichzeitig erfüllt sein. Es muss ein Objekt mit einem erforderlichen Namensfeld und ein Objekt mit einem optionalen E-Mail-Feld sein. Wie können wir das alles überprüfen?







Schreiben wir den gleichen parseUser. In diesem Fall verwenden wir die User.decode-Methode. Wir übergeben das bereits gepaarte Objekt dort, es gibt das Ergebnis an uns zurück. Vielleicht in einem ungewöhnlichen Format. Ein Objekt vom Typ Entweder kann es sich in zwei Zuständen befinden. Der erste ist richtig. Dies bedeutet normalerweise, dass alles gut gelaufen ist. links sagt, es sei nicht sehr gut gelaufen. Beide Zustände haben Eigenschaften, mit denen wir mehr lernen können. Bei Erfolg ist dies das Ergebnis der Ausführung, im Fehlerfall ein Fehler.



Wir prüfen, ob unsere Ergebnisse im linken Zustand sind. Wenn dies der Fall ist, sagen wir, dass ein Fehler aufgetreten ist. Wenn alles in Ordnung ist, geben wir einfach das Ergebnis zurück.



Fehler anzeigen







Informationen zum Anzeigen von Fehlern. Sie können es ein wenig verbessern. Wir werden dafür io-ts-Reporter verwenden. Dies ist eine Bibliothek, die vom selben Autor wie io-ts geschrieben wurde. Dadurch kann der Fehler wunderschön dargestellt werden. Was macht sie? Wir haben den Code hier geändert, wo die Ente ist. Es nimmt das Ergebnis und gibt ein Array von Zeichenfolgen zurück. Wir verbinden es einfach in einer Zeile und zeigen es an. Was bekommen wir am Ende?







Angenommen, wir übergeben null an eine JSON-Zeichenfolge.







Es werden zwei Fehler ausgegeben. Dies liegt an der Subtilität der Implementierung, da wir Schnittpunkte erstellt haben. Die Fehler sind klar genug. Beide sagen, dass wir ein Objekt erwartet haben, aber null bekommen haben. Es ist nur so, dass es für jede dieser Bedingungen einen separaten Fehler gibt.







Als nächstes versuchen wir, dort ein leeres Array zu übergeben. Es wird das gleiche sein.







Er wird uns einfach sagen: Ich habe auch ein Objekt erwartet, aber ein leeres Array erhalten.







Wir sehen also weiterhin, was passieren wird, wenn wir anfangen, falsche Daten zu übertragen. Lassen Sie uns zum Beispiel ein leeres Objekt übergeben.







Jetzt wird ein Fehler darüber angezeigt, dass wir nicht das erforderliche Namensfeld haben. Er erwartete, dass das Namensfeld vom Typ string sein würde, endete jedoch mit undefiniert. Aus diesem Fehler ist auch leicht zu verstehen, was passiert ist.







Als nächstes werden wir versuchen, dort einen falschen Typ zu übergeben. Wir erhalten auch einen Fehler, ungefähr der gleiche wie im vorherigen Beispiel.







Aber hier schreibt er uns klar die Bedeutung, die wir vermittelt haben.







Was können io-ts noch tun? Hiermit können Sie einen TypeScript-Typ abrufen. Das heißt, wir fügen diese Zeile hinzu. Durch einfaches Hinzufügen von typeof und typeof erhalten wir einen TypeScript-Typ, den wir in der Anwendung weiter verwenden können. Praktisch.







Was kann diese Bibliothek noch tun? Typen konvertieren. Angenommen, wir stellen eine Anfrage an den Server. Der Server sendet Daten im Unix-Zeitformat. Und es gibt eine spezielle Bibliothek, ebenfalls vom Ersteller der io-ts-Bibliothek: io-ts-types. Es gibt Transformationen, die ursprünglich geschrieben wurden, und Tools, mit denen diese Transformationen einfacher zu schreiben sind. Wir fügen ein Datumsfeld hinzu: Es kommt vom Server als Nummer und wird am Ende als Datumsobjekt empfangen.



Beschreiben wir den Typ



Lassen Sie uns sehen, was sich in dieser Bibliothek befindet, und versuchen, den einfachsten Typ zu beschreiben.







Lassen Sie uns zunächst sehen, wie es allgemein beschrieben wird. Es wird auf die gleiche Weise beschrieben, ziemlich kompliziert, da es auch für Transformationen benötigt wird. Abgesehen vom Server zum Client, wenn wir die Interaktion mit dem Server und die umgekehrte Transformation vom Client zum Server berücksichtigen.



Lassen Sie uns unsere Aufgabe ein wenig vereinfachen. Wir schreiben nur den Typ, der prüft. In diesem Fall wollen wir herausfinden, was diese Felder bedeuten. Name - Typ Name.







Es ist erforderlich, Fehler anzuzeigen. Wie wir in den vorherigen Beispielen gesehen haben, buchstabieren Fehler irgendwie den Namen des Typs. Sie können es hier angeben.



Als nächstes gibt es die Validierungsfunktion. Es nimmt - sagen wir vom Server - den unbekannten Wert an; nimmt einen Kontext, um den Fehler korrekt anzuzeigen; und gibt ein Entweder-Objekt in zwei Zuständen zurück - entweder einen Fehler oder einen validierten Wert.



Es gibt zwei weitere Funktionen: is und encode. Sie werden verwendet, um sie umzukehren, aber lassen Sie uns sie vorerst nicht berühren.







Wie kann der einfachste Zeichenfolgentyp dargestellt werden? Wir setzen den Namen auf string und prüfen, ob es sich um einen String handelt. Bei einer direkten Konvertierung ist dies nicht erforderlich, aber formal schreiben wir es. Und dann machen wir einfach eine Art Überprüfung. Bei Erfolg geben wir das Ergebnis erfolgreich zurück und als Ergebnis eines Fehlers einen Fehler. Der Kontext wird ebenfalls hinzugefügt, damit der Fehler korrekt angezeigt wird. Und wir geben einfach dasselbe zurück, weil es keine umgekehrte Transformation gibt.



In der Praxis



Was ist in der Praxis? Warum haben wir uns entschieden, die Daten, die vom Server stammen, überhaupt zu überprüfen?







Die Datenbank verfügt mindestens über JSON. Wir glauben natürlich, dass er gut geführt wird und an einigen Stellen überprüft wird. Aber das Format kann sich ein wenig ändern, wir dürfen das Frontend nicht brechen oder sofort Fehler feststellen, um Vergeltungsmaßnahmen zu ergreifen.



Wir haben Python auf dem Server ohne explizite Eingabe. Auch hier kann es manchmal zu kleinen Problemen kommen. Und um nicht zusammenzubrechen, können wir uns einfach überprüfen und zusätzlich sichern, nur für den Fall.



Es gibt keine eindeutige Dokumentation zu Serverantworten. Wahrscheinlich macht sich der Server mehr Sorgen darüber, was zu ihm kommt, als darüber, was er geben wird. Ja, das ist eher unser Problem - nicht zu brechen.







Was haben wir gefunden? Wir haben bereits begonnen, es ein wenig zu benutzen. Es wurde festgestellt, dass der Server uns ein leeres Objekt anstelle eines leeren Arrays gibt. Ich habe gerade den Code durchgesehen - er wurde geschrieben, um ein leeres Objekt zurückzugeben.



Weiter - das Fehlen einiger Felder. Wir dachten, sie wären obligatorisch, aber sie erweisen sich als optional.



In einigen Fällen fehlte einfach ein nullbares Feld. Das heißt, ein optionales Feld kann auf zwei Arten dargestellt werden: entweder wenn wir es einfach nicht übergeben oder wenn wir null übergeben. Es kam auch nicht immer richtig zu uns. Um Fehler in der Mitte unseres Codes nicht abzufangen, können wir dies nur auf Anfrage abfangen.







Was haben wir jetzt? Wir haben bereits viele Antworten vom Server überprüft und uns angemeldet, wenn uns etwas nicht gefällt. Dann analysieren wir dies und legen Aufgaben fest: entweder zum Ändern der Eingabe in unserem Frontend oder zum Bearbeiten im Backend. Jetzt ändern wir nicht die Daten, die vom Server kommen: Wenn null anstelle einer Zeichenfolge kam, ändern wir sie beispielsweise nicht in eine leere Zeichenfolge.



Wir planen zu überprüfen und zu protokollieren, aber zu korrigieren, wenn ein Fehler vorliegt. Wenn wir falsche Daten erhalten, korrigieren wir diesen Wert, damit Benutzer zumindest etwas anzeigen können, anstatt in unseren Code zu fallen.







Kleine Ergebnisse. Wir aktivieren strikt, damit TypeScript uns mehr hilft, as, any und das Ausrufezeichen auszuschließen. Wir werden mit Arrays und Objekten in TypeScript vorsichtiger sein und auch alle externen Daten überprüfen. Das sind übrigens nicht nur Server. Sie können auch localStorage überprüfen, Nachrichten, die in Ereignissen eingehen. Zum Beispiel postMessage.



Vielen Dank für Ihre Aufmerksamkeit.



All Articles