TypeScript meistern
Bitte unterstützen Sie dieses Buch: kaufen Sie es oder spenden Sie
(Werbung, bitte nicht blockieren.)

21 Typzusicherungen (verwandt mit Castings)



Dieses Kapitel behandelt Typzusicherungen in TypeScript, die mit Typ-Casts in anderen Sprachen verwandt sind und über den as-Operator ausgeführt werden.

21.1 Typzusicherungen

Eine Typzusicherung erlaubt es uns, einen statischen Typ zu überschreiben, den TypeScript für einen Wert berechnet hat. Das ist nützlich, um Einschränkungen des Typsystems zu umgehen.

Typzusicherungen sind mit Typ-Casts in anderen Sprachen verwandt, werfen jedoch keine Ausnahmen und bewirken zur Laufzeit nichts (sie führen einige minimale Prüfungen statisch durch).

const data: object = ['a', 'b', 'c']; // (A)

// @ts-expect-error: Property 'length' does not exist on type 'object'.
data.length; // (B)

assert.equal(
  (data as Array<string>).length, 3); // (C)

Kommentare

Typzusicherungen sind ein letzter Ausweg und sollten so weit wie möglich vermieden werden. Sie entfernen (vorübergehend) das Sicherheitsnetz, das uns das statische Typsystem normalerweise bietet.

Beachten Sie, dass wir in Zeile A auch den statischen Typ von TypeScript überschrieben haben. Dies geschah jedoch über eine Typannotation. Diese Art der Überschreibung ist viel sicherer als Typzusicherungen, da wir viel stärker eingeschränkt sind: Der Typ von TypeScript muss dem Typ der Annotation zuweisbar sein.

21.1.1 Alternative Syntax für Typzusicherungen

TypeScript hat eine alternative "Angle-Bracket"-Syntax für Typzusicherungen.

<Array<string>>data

Ich empfehle, diese Syntax zu vermeiden. Sie ist aus der Mode gekommen und nicht mit React JSX-Code (in .tsx-Dateien) kompatibel.

21.1.2 Beispiel: Zusicherung eines Interfaces

Um auf die Eigenschaft .name eines beliebigen Objekts obj zuzugreifen, ändern wir vorübergehend den statischen Typ von obj zu Named (Zeile A und Zeile B).

interface Named {
  name: string;
}
function getName(obj: object): string {
  if (typeof (obj as Named).name === 'string') { // (A)
    return (obj as Named).name; // (B)
  }
  return '(Unnamed)';
}

21.1.3 Beispiel: Zusicherung einer Index-Signatur

Im folgenden Code (Zeile A) verwenden wir die Typzusicherung as Dict, damit wir auf die Eigenschaften eines Werts zugreifen können, dessen abgeleiteter Typ object ist. Das heißt, wir überschreiben den statischen Typ object mit dem statischen Typ Dict.

type Dict = {[k:string]: any};

function getPropertyValue(dict: unknown, key: string): any {
  if (typeof dict === 'object' && dict !== null && key in dict) {
    // %inferred-type: object
    dict;

    // @ts-expect-error: Element implicitly has an 'any' type because
    // expression of type 'string' can't be used to index type '{}'.
    // [...]
    dict[key];
    
    return (dict as Dict)[key]; // (A)
  } else {
    throw new Error();
  }
}

21.2.1 Non-nullish Assertion Operator (Postfix !)

Wenn der Typ eines Werts eine Union ist, die die Typen undefined oder null enthält, entfernt der Non-nullish Assertion Operator (oder Non-null Assertion Operator) diese Typen aus der Union. Wir sagen TypeScript: „Dieser Wert kann nicht undefined oder null sein.“ Folglich können wir Operationen durchführen, die durch die Typen dieser beiden Werte verhindert werden – zum Beispiel

const theName = 'Jane' as (null | string);

// @ts-expect-error: Object is possibly 'null'.
theName.length;

assert.equal(
  theName!.length, 4); // OK
21.2.1.1 Beispiel – Maps: .get() nach .has()

Nachdem wir die Map-Methode .has() verwendet haben, wissen wir, dass eine Map einen bestimmten Schlüssel hat. Leider spiegelt das Ergebnis von .get() diese Kenntnis nicht wider, weshalb wir den Nullish Assertion Operator verwenden müssen.

function getLength(strMap: Map<string, string>, key: string): number {
  if (strMap.has(key)) {
    // We are sure x is not undefined:
    const value = strMap.get(key)!; // (A)
    return value.length;
  }
  return -1;
}

Wir können den Nullish Assertion Operator vermeiden, wenn die Werte einer Map nicht undefined sein können. Dann können fehlende Einträge erkannt werden, indem geprüft wird, ob das Ergebnis von .get() undefined ist.

function getLength(strMap: Map<string, string>, key: string): number {
  // %inferred-type: string | undefined
  const value = strMap.get(key);
  if (value === undefined) { // (A)
    return -1;
  }

  // %inferred-type: string
  value;

  return value.length;
}

21.2.2 Definite Assignment Assertions

Wenn strict property initialization aktiviert ist, müssen wir TypeScript gelegentlich mitteilen, dass wir bestimmte Eigenschaften initialisieren – auch wenn es denkt, dass wir es nicht tun.

Dies ist ein Beispiel, bei dem TypeScript sich beschwert, obwohl es das nicht sollte.

class Point1 {
  // @ts-expect-error: Property 'x' has no initializer and is not definitely
  // assigned in the constructor.
  x: number;

  // @ts-expect-error: Property 'y' has no initializer and is not definitely
  // assigned in the constructor.
  y: number;

  constructor() {
    this.initProperties();
  }
  initProperties() {
    this.x = 0;
    this.y = 0;
  }
}

Die Fehler verschwinden, wenn wir Definite Assignment Assertions (Ausrufezeichen) in Zeile A und Zeile B verwenden.

class Point2 {
  x!: number; // (A)
  y!: number; // (B)
  constructor() {
    this.initProperties();
  }
  initProperties() {
    this.x = 0;
    this.y = 0;
  }
}