Typenprädikate sind eine interessante syntaktische Eigenschaft in TypeScript. Obwohl sie in derselben Stelle wie Rückgabetypangaben erscheinen, sieht man in ihnen eher kurze positive Sätze als typische Annotierungen. Dies gibt Ihnen eine größere Kontrolle über die Typenüberprüfung.

Mit der Veröffentlichung von TypeScript 5.5 ist der Umgang mit Typenprädikaten intuitiver geworden, da es sie in vielen Fällen automatisch ableiten kann. Wenn Sie jedoch etwas ältere Codebasen durchsuchen, ist es wahrscheinlicher, dass Sie häufiger manuell geschriebene Typenprädikate finden werden.

In diesem Artikel werden wir kurz erörtert, was Typenprädikate sind und warum sie nützlich sind. Lassen Sie uns mit der Lösung eines Problems beginnen, das sie lösen.

Das Problem

Der beste Weg, um die Nützlichkeit von Typenprädikaten zu verstehen, besteht meiner Meinung nach darin, die Probleme zu bemerken, die auftreten, wenn wir sie nicht haben:

function isString(value: unknown): boolean {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

Hier ist der Rückgabetyp von isString auf boolean festgelegt, und wir verwenden es in der Funktion padLeft, um Padding links an einer Eingabestring-Zeile hinzuzufügen. Der padding kann entweder eine bestimmte Zeichenkette sein oder eine angegebene Anzahl von Leerzeichen.

Vielleicht fragen Sie sich, warum ich den Rückgabetyp auf boolean fest codiert habe. Dies dient zur Veranschaulichung des Problems. Wenn Sie keine Rückgabetyp-Anmerkung hinzufügen und die neueste Version von TypeScript verwenden, werden Sie hier kein Problem bemerken. Im Moment haben Sie Geduld mit mir – wir werden die versionsbezogenen Unterschiede gleich besprechen.

Die Funktion wird zur Laufzeit reibungslos funktionieren, aber TypeScript kann keine Typverengung mit isString durchführen. Infolgedessen bleibt der Typ von padding sowohl innerhalb als auch außerhalb der if-Anweisung string | number. Dies führt zu einem Konflikt mit den Erwartungen von repeat an sein erstes Argument und verursacht den Typfehler.

Die Lösung: Eintreten von Typprädikaten

Selbst wenn Sie mit dem Begriff Prädikat nicht vertraut sind, haben Sie sie wahrscheinlich schon einmal verwendet. Prädikate in der Programmierung sind einfach Funktionen, die einen Boolean-Wert zurückgeben, um eine Ja/Nein-Frage zu beantworten. Mehrere eingebaute JavaScript-Array-Methoden wie filter, find, every und some verwenden Prädikate, um bei der Entscheidungsfindung zu helfen.

Typprädikate sind eine Möglichkeit, um Prädikate nützlicher für die Typverengung zu machen. Wir können das Problem beheben, indem wir ein Typprädikat als Rückgabetyp verwenden:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

Hier ist das Typprädikat value is string. Es sagt drei Dinge aus:

  • Die Funktion ist ein Prädikat. TypeScript zeigt also einen Fehler an, wenn Sie versuchen, etwas anderes als einen Boolean-Wert zurückzugeben.

  • Wenn es true zurückgibt, dann ist value vom Typ String.

  • Wenn es false zurückgibt, dann ist value nicht vom Typ String.

Typprädikate ermöglichen die Erstellung benutzerdefinierten Typenschutzes. Typenschutze sind logische Überprüfungen, die es Ihnen ermöglichen, Typen auf beschränkter Typenbildung zu verfeinern, d.h. zu verkleinern. Daher ist die oben genannte Funktion auch ein benutzerdefiniertes Typenschutz-Muster.

Hier ist der komplette Code:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // String
  }
  return " ".repeat(padding) + input;
                 //   ^
                 // Number
}

Hier korrekt Typescript den Typ von padding innerhalb und außerhalb des if-Ausdrucks verfeinert.

Nun lassen Sie uns kurz sehen, wie Typprädikate früher in TypeScript 5.5 funktioniert haben und was diese Version verbessert hat.

Typprädikate vor TypeScript 5.5

In unserem vorherigen Beispiel, wenn wir keinen Rückgabetyp angeben, wird er als boolean abgeleitet:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // String | Number
  }
  return " ".repeat(padding) + input; // Opps Typfehler hier
                 //   ^
                 // String | Number
}

Daher haben wir das gleiche Fehler, wie wenn wir die Rückgabetyp boolean manuell geschrieben hätten. Hier ist der Link zum TypeScript-Spielwiese für den obenstehenden Codeausschnitt. Gehen Sie und hoverie den Funktionen oder Variablen für ein besseres Gefühl der Typen zu. Dann sehen wir, wie das Schreiben der Typprädikate das Problem löst.

Wenn wir die Typprädikate nicht angeben, kann die Nutzung von Methoden wie filter auch zu falscher Typerkennung führen:

function isString(value: unknown) {
  return typeof value === "string";
}

const numsOrStrings = [1, 'hello', 2, 'world'];
//      ^
//    strings: (string | number)[]

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: (string | number)[]

Nun sehen wir, wie TypeScript 5.5 die Situation verbessert.

Typprädikate nach TypeScript 5.5

Eine der Hauptfunktionen von TypeScript 5.5 ist, dass es Typprädikate durch Analyse des Funktionskörpers ableiten kann. Also, wenn Sie TypeScript 5.5 oder neuer verwenden, müssen Sie das Typprädikat nicht als Rückgabetyp von isString schreiben. TypeScript tut es für Sie, und der Code wie in dem Beispiel unten funktioniert perfekt:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // Zeichenkette
  }
  return " ".repeat(padding) + input; // Ups, hier ein Typfehler
                 //   ^
                 // Nummer
}

const numsOrStrings = [1, 'hello', 2, 'world'];

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: string[]

const numbers = numsOrStrings.filter((v) => !isString(v));
//      ^
//    numbers: number[]

Ich habe noch keine Situation gefunden, in der ich mit der automatischen Inferenz von Typprädikaten unzufrieden war. Falls Sie eine finden, können Sie immer Ihre eigenen manuell schreiben.

Weiterführendes Studium

In diesem Artikel haben wir kurz Typprädikate in TypeScript untersucht. Wenn Sie daran interessiert sind, mehr zu lernen und die Randfälle zu verstehen, finden Sie hier die offiziellen Anleitungen:

Vielen Dank fürs Lesen! Bis zum nächsten Mal!

Das Coverfoto-Hintergrundbild stammt von Mona Eendra auf Unsplash