paint-brush
Die Leistungsfähigkeit von TypeScript entfesseln: Wichtige Überlegungen in tsconfigvon@nodge
2,174 Lesungen
2,174 Lesungen

Die Leistungsfähigkeit von TypeScript entfesseln: Wichtige Überlegungen in tsconfig

von Maksim Zemskov13m2023/07/12
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

TypeScript ist dank seines starken Typsystems und seiner statischen Analysefunktionen eine beliebte Sprache zum Erstellen komplexer Anwendungen. Um jedoch maximale Typsicherheit zu erreichen, ist es wichtig, tsconfig richtig zu konfigurieren. In diesem Artikel besprechen wir die wichtigsten Überlegungen zur Konfiguration von tsconfig, um eine optimale Typsicherheit zu erreichen.
featured image - Die Leistungsfähigkeit von TypeScript entfesseln: Wichtige Überlegungen in tsconfig
Maksim Zemskov HackerNoon profile picture
0-item
1-item

Wenn Sie komplexe Webanwendungen erstellen, ist TypeScript wahrscheinlich die Programmiersprache Ihrer Wahl. TypeScript ist bekannt für sein starkes Typsystem und seine statischen Analysefunktionen, was es zu einem leistungsstarken Werkzeug macht, um sicherzustellen, dass Ihr Code robust und fehlerfrei ist.


Darüber hinaus beschleunigt es den Entwicklungsprozess durch die Integration mit Code-Editoren, sodass Entwickler effizienter durch den Code navigieren können und genauere Hinweise und automatische Vervollständigung erhalten sowie ein sicheres Refactoring großer Codemengen ermöglichen.


Der Compiler ist das Herzstück von TypeScript und verantwortlich für die Überprüfung der Typkorrektheit und die Umwandlung von TypeScript-Code in JavaScript. Um jedoch die Leistungsfähigkeit von TypeScript voll auszunutzen, ist es wichtig, den Compiler richtig zu konfigurieren.


Jedes TypeScript-Projekt verfügt über eine oder mehrere tsconfig.json Dateien, die alle Konfigurationsoptionen für den Compiler enthalten.


Die Konfiguration von tsconfig ist ein entscheidender Schritt, um eine optimale Typsicherheit und Entwicklererfahrung in Ihren TypeScript-Projekten zu erreichen. Indem Sie sich die Zeit nehmen, alle wichtigen Faktoren sorgfältig abzuwägen, können Sie den Entwicklungsprozess beschleunigen und sicherstellen, dass Ihr Code robust und fehlerfrei ist.

Nachteile der Standardkonfiguration

Die Standardkonfiguration in tsconfig kann dazu führen, dass Entwickler die meisten Vorteile von TypeScript verpassen. Dies liegt daran, dass viele leistungsstarke Funktionen zur Typprüfung nicht möglich sind. Mit „Standard“-Konfiguration meine ich eine Konfiguration, bei der keine Typprüfungs-Compileroptionen festgelegt sind.


Zum Beispiel:


 { "compilerOptions": { "target": "esnext", "module": "esnext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, }, "include": ["src"] }


Das Fehlen mehrerer wichtiger Konfigurationsoptionen kann aus zwei Hauptgründen zu einer geringeren Codequalität führen. Erstens kann der Compiler von TypeScript in verschiedenen Fällen null und undefined Typen falsch verarbeiten.


Zweitens kann der Typ any unkontrolliert in Ihrer Codebasis auftauchen, was dazu führt, dass die Typprüfung für diesen Typ deaktiviert wird.


Glücklicherweise lassen sich diese Probleme leicht beheben, indem Sie einige Optionen in der Konfiguration anpassen.

Der strenge Modus

 { "compilerOptions": { "strict": true } }


Der strikte Modus ist eine wesentliche Konfigurationsoption, die stärkere Garantien für die Programmkorrektheit bietet, indem sie eine breite Palette von Verhaltensweisen bei der Typprüfung ermöglicht.


Die Aktivierung des strikten Modus in der tsconfig-Datei ist ein entscheidender Schritt zur Erreichung maximaler Typsicherheit und einer besseren Entwicklererfahrung.


Die Konfiguration von tsconfig erfordert etwas mehr Aufwand, kann aber die Qualität Ihres Projekts erheblich verbessern.


Die strict Compileroption aktiviert alle Optionen der Strict-Mode-Familie, zu denen unter anderem noImplicitAny , strictNullChecks und strictFunctionTypes gehören.


Diese Optionen können auch separat konfiguriert werden, es wird jedoch nicht empfohlen, eine davon zu deaktivieren. Schauen wir uns Beispiele an, um zu sehen, warum.

Implizite Schlussfolgerungen

 { "compilerOptions": { "noImplicitAny": true } }


Der Typ any ist eine gefährliche Lücke im statischen Typsystem und seine Verwendung deaktiviert alle Typprüfungsregeln. Dadurch gehen alle Vorteile von TypeScript verloren: Fehler werden übersehen, Code-Editor-Hinweise funktionieren nicht mehr richtig und so weiter.


Die Verwendung von any ist nur in extremen Fällen oder für Prototyping-Anforderungen in Ordnung. Trotz unserer besten Bemühungen kann sich der Typ any manchmal implizit in eine Codebasis einschleichen.


Standardmäßig verzeiht uns der Compiler viele Fehler im Gegenzug dafür, dass any in einer Codebasis auftauchen. Insbesondere ermöglicht uns TypeScript, den Typ von Variablen nicht anzugeben, selbst wenn der Typ nicht automatisch abgeleitet werden kann.


Das Problem besteht darin, dass wir versehentlich vergessen, den Typ einer Variablen anzugeben, beispielsweise für ein Funktionsargument. Anstatt einen Fehler anzuzeigen, leitet TypeScript automatisch den Typ der Variablen als any ab.


 function parse(str) { // ^? any return str.split(''); } // TypeError: str.split is not a function const res1 = parse(42); const res2 = parse('hello'); // ^? any


Wenn Sie die Compileroption noImplicitAny aktivieren, markiert der Compiler alle Stellen, an denen der Typ einer Variablen automatisch als any abgeleitet wird. In unserem Beispiel fordert uns TypeScript auf, den Typ für das Funktionsargument anzugeben.


 function parse(str) { // ^ Error: Parameter 'str' implicitly has an 'any' type. return str.split(''); }


Wenn wir den Typ angeben, erkennt TypeScript schnell den Fehler, eine Zahl an einen String-Parameter zu übergeben. Auch der Rückgabewert der Funktion, der in der Variablen res2 gespeichert ist, hat den richtigen Typ.


 function parse(str: string) { return str.split(''); } const res1 = parse(42); // ^ Error: Argument of type 'number' is not // assignable to parameter of type 'string' const res2 = parse('hello'); // ^? string[]


Unbekannter Typ in Catch-Variablen

 { "compilerOptions": { "useUnknownInCatchVariables": true } }


Die Konfiguration useUnknownInCatchVariables ermöglicht die sichere Behandlung von Ausnahmen in Try-Catch-Blöcken. Standardmäßig geht TypeScript davon aus, dass der Fehlertyp in einem Catch-Block any ist, sodass wir mit dem Fehler alles machen können.


Beispielsweise könnten wir den abgefangenen Fehler unverändert an eine Protokollierungsfunktion übergeben, die eine Instanz von Error akzeptiert.


 function logError(err: Error) { // ... } try { return JSON.parse(userInput); } catch (err) { // ^? any logError(err); }


In der Realität gibt es jedoch keine Garantien für die Art des Fehlers, und wir können seinen wahren Typ erst zur Laufzeit bestimmen, wenn der Fehler auftritt. Wenn die Protokollierungsfunktion etwas empfängt, das kein Error ist, führt dies zu einem Laufzeitfehler.


Daher ändert die Option useUnknownInCatchVariables “ den Fehlertyp von any auf „ unknown “, um uns daran zu erinnern, den Fehlertyp zu überprüfen, bevor wir etwas dagegen unternehmen.


 try { return JSON.parse(userInput); } catch (err) { // ^? unknown // Now we need to check the type of the value if (err instanceof Error) { logError(err); } else { logError(new Error('Unknown Error')); } }


Jetzt fordert uns TypeScript auf, den Typ der err Variablen zu überprüfen, bevor wir sie an die logError Funktion übergeben, was zu korrekterem und sichererem Code führt. Leider hilft diese Option nicht bei Tippfehlern in promise.catch() -Funktionen oder Callback-Funktionen.


Aber wir werden im nächsten Artikel any besprechen, mit solchen Fällen umzugehen.

Typprüfung für die Call- und Apply-Methoden

 { "compilerOptions": { "strictBindCallApply": true } }


Eine weitere Option korrigiert das Erscheinungsbild any funktionsinternen Aufrufe über call und apply . Dies ist ein seltenerer Fall als die ersten beiden, aber es ist dennoch wichtig, ihn zu berücksichtigen. Standardmäßig prüft TypeScript überhaupt keine Typen in solchen Konstruktionen.


Wir können beispielsweise alles als Argument an eine Funktion übergeben und erhalten am Ende immer den Typ any .


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? any const n2 = parse.call(undefined, false); // ^? any


Durch die Aktivierung der strictBindCallApply Option wird TypeScript intelligenter, sodass der Rückgabetyp korrekt als number abgeleitet wird. Und wenn versucht wird, ein Argument des falschen Typs zu übergeben, zeigt TypeScript auf den Fehler.


 function parse(value: string) { return parseInt(value, 10); } const n1 = parse.call(undefined, '10'); // ^? number const n2 = parse.call(undefined, false); // ^ Argument of type 'boolean' is not // assignable to parameter of type 'string'.


Strikte Typen für den Ausführungskontext

 { "compilerOptions": { "noImplicitThis": true } }


Die nächste Option, die dabei helfen kann, das Auftreten von any Fehlern in Ihrem Projekt zu verhindern, korrigiert die Handhabung des Ausführungskontexts in Funktionsaufrufen. Aufgrund der dynamischen Natur von JavaScript ist es schwierig, den Kontexttyp innerhalb einer Funktion statisch zu bestimmen.


Standardmäßig verwendet TypeScript in solchen Fällen den Typ any für den Kontext und gibt keine Warnungen aus.


 class Person { private name: string; constructor(name: string) { this.name = name; } getName() { return function () { return this.name; // ^ 'this' implicitly has type 'any' because // it does not have a type annotation. }; } }


Durch Aktivieren der Compileroption noImplicitThis werden wir aufgefordert, den Kontexttyp für eine Funktion explizit anzugeben. Auf diese Weise können wir im obigen Beispiel den Fehler abfangen, der beim Zugriff auf den Funktionskontext statt auf das name der Person Klasse entsteht.


Null- und undefinierte Unterstützung in TypeScript

 { "compilerOptions": { "strictNullChecks": true } }


Als nächstes führen mehrere Optionen, die im strict Modus enthalten sind, nicht dazu, dass der Typ „ any in der Codebasis erscheint. Allerdings verschärfen sie das Verhalten des TS-Compilers und ermöglichen das Auffinden von Fehlern während der Entwicklung.


Die erste dieser Optionen korrigiert die Behandlung von null und undefined in TypeScript. Standardmäßig geht TypeScript davon aus, dass null und undefined gültige Werte für jeden Typ sind, was zu unerwarteten Laufzeitfehlern führen kann.


Durch die Aktivierung der Compileroption strictNullChecks wird der Entwickler gezwungen, explizit Fälle zu behandeln, in denen null und undefined auftreten können.


Betrachten Sie beispielsweise den folgenden Code:


 const users = [ { name: 'Oby', age: 12 }, { name: 'Heera', age: 32 }, ]; const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } console.log(loggedInUser.age); // ^ TypeError: Cannot read properties of undefined


Dieser Code wird fehlerfrei kompiliert, kann jedoch einen Laufzeitfehler auslösen, wenn der Benutzer mit dem Namen „Max“ nicht im System vorhanden ist und users.find() undefined zurückgibt. Um dies zu verhindern, können wir die Compileroption strictNullChecks aktivieren.


Jetzt zwingt uns TypeScript dazu, explizit mit der Möglichkeit umzugehen, dass null oder undefined von users.find() zurückgegeben werden.


 const loggedInUser = users.find(u => u.name === 'Max'); // ^? { name: string; age: number; } | undefined if (loggedInUser) { console.log(loggedInUser.age); }


Durch den expliziten Umgang mit der Möglichkeit von null und undefiined können wir Laufzeitfehler vermeiden und sicherstellen, dass unser Code robuster und fehlerfrei ist.

Strikte Funktionstypen

 { "compilerOptions": { "strictFunctionTypes": true } }


Durch die Aktivierung strictFunctionTypes wird der Compiler von TypeScript intelligenter. Vor Version 2.6 hat TypeScript die Kontravarianz von Funktionsargumenten nicht überprüft. Dies führt zu Laufzeitfehlern, wenn die Funktion mit einem Argument des falschen Typs aufgerufen wird.


Selbst wenn ein Funktionstyp beispielsweise sowohl Zeichenfolgen als auch Zahlen verarbeiten kann, können wir diesem Typ eine Funktion zuweisen, die nur Zeichenfolgen verarbeiten kann. Wir können dieser Funktion immer noch eine Zahl übergeben, erhalten jedoch einen Laufzeitfehler.


 function greet(x: string) { console.log("Hello, " + x.toLowerCase()); } type StringOrNumberFn = (y: string | number) => void; // Incorrect Assignment const func: StringOrNumberFn = greet; // TypeError: x.toLowerCase is not a function func(10);


Glücklicherweise behebt die Aktivierung der Option strictFunctionTypes dieses Verhalten, und der Compiler kann diese Fehler zur Kompilierungszeit abfangen und uns eine detaillierte Meldung über die Typinkompatibilität in Funktionen anzeigen.


 const func: StringOrNumberFn = greet; // ^ Type '(x: string) => void' is not assignable to type 'StringOrNumberFn'. // Types of parameters 'x' and 'y' are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'.


Initialisierung von Klasseneigenschaften

 { "compilerOptions": { "strictPropertyInitialization": true } }


Zu guter Letzt ermöglicht die Option strictPropertyInitialization die Überprüfung der obligatorischen Klasseneigenschaftsinitialisierung für Typen, die undefined nicht als Wert enthalten.


Im folgenden Code hat der Entwickler beispielsweise vergessen, die email Eigenschaft zu initialisieren. Standardmäßig würde TypeScript diesen Fehler nicht erkennen und es könnte zur Laufzeit ein Problem auftreten.


 class UserAccount { name: string; email: string; constructor(name: string) { this.name = name; // Forgot to assign a value to this.email } }


Wenn jedoch die Option strictPropertyInitialization aktiviert ist, wird TypeScript dieses Problem für uns hervorheben.


 email: string; // ^ Error: Property 'email' has no initializer and // is not definitely assigned in the constructor.

Sichere Indexsignaturen

 { "compilerOptions": { "noUncheckedIndexedAccess": true } }


Die Option noUncheckedIndexedAccess ist nicht Teil des strict Modus, aber eine weitere Option, die zur Verbesserung der Codequalität in Ihrem Projekt beitragen kann. Es ermöglicht die Überprüfung von Indexzugriffsausdrücken auf einen null oder undefined Rückgabetyp, wodurch Laufzeitfehler verhindert werden können.


Betrachten Sie das folgende Beispiel, in dem wir ein Objekt zum Speichern zwischengespeicherter Werte haben. Wir erhalten dann den Wert für einen der Schlüssel. Natürlich können wir nicht garantieren, dass der Wert für den gewünschten Schlüssel tatsächlich im Cache vorhanden ist.


Standardmäßig geht TypeScript davon aus, dass der Wert existiert und den Typ string hat. Dies kann zu einem Laufzeitfehler führen.


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string console.log(value.toUpperCase()); // ^ TypeError: Cannot read properties of undefined


Das Aktivieren der Option noUncheckedIndexedAccess in TypeScript erfordert die Überprüfung von Indexzugriffsausdrücken auf undefined Rückgabetypen, was uns helfen kann, Laufzeitfehler zu vermeiden. Dies gilt auch für den Zugriff auf Elemente in einem Array.


 const cache: Record<string, string> = {}; const value = cache['key']; // ^? string | undefined if (value) { console.log(value.toUpperCase()); }

Empfohlene Konfiguration

Basierend auf den besprochenen Optionen wird dringend empfohlen, die Optionen strict und noUncheckedIndexedAccess in tsconfig.json Datei Ihres Projekts zu aktivieren, um optimale Typsicherheit zu gewährleisten.


 { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, } }


Wenn Sie die Option strict bereits aktiviert haben, können Sie erwägen, die folgenden Optionen zu entfernen, um eine Duplizierung der Option strict: true zu vermeiden:


  • noImplicitAny
  • useUnknownInCatchVariables
  • strictBindCallApply
  • noImplicitThis
  • strictFunctionTypes
  • strictNullChecks
  • strictPropertyInitialization


Es wird außerdem empfohlen, die folgenden Optionen zu entfernen, die das Typsystem schwächen oder Laufzeitfehler verursachen können:


  • keyofStringsOnly
  • noStrictGenericChecks
  • suppressImplicitAnyIndexErrors
  • suppressExcessPropertyErrors


Durch sorgfältiges Abwägen und Konfigurieren dieser Optionen können Sie optimale Typsicherheit und ein besseres Entwicklererlebnis in Ihren TypeScript-Projekten erreichen.

Abschluss

TypeScript hat in seiner Entwicklung einen langen Weg zurückgelegt und seinen Compiler und sein Typsystem ständig verbessert. Um die Abwärtskompatibilität zu gewährleisten, ist die TypeScript-Konfiguration jedoch komplexer geworden und bietet viele Optionen, die die Qualität der Typprüfung erheblich beeinträchtigen können.


Durch sorgfältiges Abwägen und Konfigurieren dieser Optionen können Sie optimale Typsicherheit und ein besseres Entwicklererlebnis in Ihren TypeScript-Projekten erreichen. Es ist wichtig zu wissen, welche Optionen in einer Projektkonfiguration aktiviert und entfernt werden müssen.


Wenn Sie die Folgen der Deaktivierung bestimmter Optionen verstehen, können Sie für jede einzelne Option fundierte Entscheidungen treffen.


Es ist wichtig zu bedenken, dass eine strikte Eingabe Konsequenzen haben kann. Um effektiv mit der dynamischen Natur von JavaScript umgehen zu können, müssen Sie über gute Kenntnisse von TypeScript verfügen, die über die einfache Angabe von „Zahl“ oder „Zeichenfolge“ nach einer Variablen hinausgehen.


Sie müssen mit komplexeren Konstrukten und dem TypeScript-First-Ökosystem von Bibliotheken und Tools vertraut sein, um auftretende typbezogene Probleme effektiver lösen zu können.


Daher ist das Schreiben von Code möglicherweise etwas aufwändiger, aber meiner Erfahrung nach lohnt sich dieser Aufwand für langfristige Projekte.


Ich hoffe, Sie haben aus diesem Artikel etwas Neues gelernt. Dies ist der erste Teil einer Serie. Im nächsten Artikel besprechen wir, wie wir durch die Verbesserung der Typen in der Standardbibliothek von TypeScript eine bessere Typsicherheit und Codequalität erreichen können. Bleiben Sie dran und vielen Dank fürs Lesen!

Nützliche Links