ProgrammierungFrontend/Backend Entwickler

Wie ist die Strict Function Types-Option in TypeScript aufgebaut und wie funktioniert sie? Wie beeinflusst sie die Typüberprüfung von Funktionen mit Kovariante und Kontravarianz, und in welchen Fällen führt eine Nichtübereinstimmung der Signaturen zu einem Kompilierungsfehler?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort.

Historie der Frage

Standardmäßig erlaubt TypeScript eine gewisse "Schludrigkeit" bei der Zuordnung von Typensignaturen von Funktionen, indem es zulässt, dass konforme und nicht konforme Funktionen als kompatibel betrachtet werden. Mit TypeScript 2.6 wurde die Option strictFunctionTypes eingeführt, die eine strenge Typüberprüfung von Funktionen garantiert und eine Vielzahl von Fehlerklassen verhindert, insbesondere in großen Codebasen.

Problem

Ohne strenge Überprüfung kann es vorkommen, dass eine Handler- oder Callback-Funktion einen größeren oder spezifischeren Typ von Parametern akzeptiert, und dies bleibt für den Entwickler unbemerkt. Dies führt zu Laufzeitfehlern im Zusammenhang mit der Kovariante von Rückgabewerten und der Kontravarianz von Argumenten.

Lösung

Die Option strictFunctionTypes führt eine strenge Kontravarianz für die Typen der Funktionsparameter ein. Jetzt sind Funktionen nur dann kompatibel, wenn der Typ des Quellparameters ein Supertyp des Zielparameters ist und nicht umgekehrt.

Beispielcode:

type Animal = { name: string }; type Cat = { name: string; meow: () => void }; let animalHandler: (a: Animal) => void; let catHandler: (c: Cat) => void; animalHandler = catHandler; // Fehler bei strictFunctionTypes: Argument ist zu spezifisch catHandler = animalHandler; // Erlaubt, Cat ist ein Subtyp von Animal

Wesentliche Merkmale:

  • Die Argumente von Funktionen werden auf die Kompatibilität von "Supertypen" (Kontravarianz) überprüft
  • Die Rückgabewerte werden auf Kovariante (Subtypen sind erlaubt) ausgewertet
  • Verletzungen der Signaturen führen zu Kompilierungsfehlern

Fangfragen.

War es früher möglich, einen Handler mit einem spezifischeren Typ von Parameter zuzuweisen, bevor strictFunctionTypes eingeführt wurde?

Ja, vor der Aktivierung von strictFunctionTypes erlaubte TypeScript die Zuweisung spezifischerer Funktionen an allgemeinere, was zu Laufzeitproblemen führte:

enum E { A, B } const f: (e: E) => void = (e: E.A) => {} // Ohne strictFunctionTypes: erlaubt

Wie beeinflusst strictFunctionTypes Callbacks mit optionalen Parametern?

Wenn Parameter der Callback-Funktion einige Parameter optional machen, verhindert die strenge Überprüfung die Verwendung einer Funktion mit weniger obligatorischen Parametern an einer Stelle, wo eine Funktion mit mehr erwartet wird. Dies verhindert, dass der Callback nicht die benötigten Daten erhält.

Wird es Kompatibilitätsprobleme bei der Aktivierung von strictFunctionTypes in alten Projekten geben?

Ja, es besteht das Risiko, dass neue Kompilierungsfehler auftreten, da viele Funktionen und Handler möglicherweise einander mit Verletzung der Kontravarianz zugewiesen wurden. Dies tritt häufig bei Rückrufen oder bei der Verwendung von APIs von Drittanbieter-Bibliotheken ohne strikte Typisierung auf.

Typische Fehler und Anti-Pattern

  • Verwendung von veralteter Typisierung bei Callbacks ohne strenge Parameterüberprüfung
  • Versuch, eine Funktion mit einem spezifischeren/engerem Typ von Parameter einem allgemeineren Handler zuzuweisen
  • Ignorierung von Fehlern bei Aktivierung von strictFunctionTypes (Option kommentieren anstelle von Typkorrekturen)

Beispiel aus dem Leben

Negativer Fall

In einem großen Projekt akzeptieren Ereignis-Handler spezifischere Typen (MouseEvent statt des allgemeinen Event). Dies wird erst bei Aktivierung der strengen Option erkannt, was zu Fehlern beim Start mit verschiedenen Ereignisquellen führt.

Vorteile:

  • Schnellere Prototypenerstellung

Nachteile:

  • Laufzeitfehler bei Typinkompatibilität von Ereignissen
  • Schwierige Fehlersuche nach einer Codeerweiterung

Positiver Fall

Das Projekt verwendet seit Beginn strictFunctionTypes. Bei der Hinzufügung neuer Handler werden alle Unterschiede zwischen den Typen automatisch vom Compiler erkannt. Der Code wird stabiler gegenüber Tippfehlern und ist einfacher zu warten.

Vorteile:

  • Zuverlässigkeit
  • Sicherheit bei der Übergabe von Funktionen und Handlern
  • Vorhersehbares Verhalten beim Refactoring

Nachteile:

  • Erfordert sorgfältige Planung von Signaturen
  • In einigen Fällen müssen zusätzliche Wrapper oder Überladungen für die Kompatibilität geschrieben werden.