Menu

TypeScript typeof: Type Guards und der Typoperator typeof

typeof hat in TypeScript zwei Aufgaben. Im Code ist es der JavaScript-Operator, der zur Laufzeit "string", "number", "object" und so weiter zurückgibt, und TypeScript engt anhand davon ein. In einem Typ ist es die Type Query, die den Typ einer Variablen kopiert, wie in keyof typeof obj und ReturnType<typeof fn>.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

typeof bedeutet in TypeScript zwei Dinge. In einem Ausdruck ist es der JavaScript-Operator: Er läuft, gibt einen String wie "string" oder "number" zurück, und TypeScript engt die Variable ein, wenn du diesen String vergleichst. An einer Typposition ist es eine Type Query: typeof config liefert den statischen Typ der Variablen config.

Die letzte Zeile zeigt den Unterschied: typeof custom in einem console.log ist der Laufzeitoperator und gibt object aus, während typeof defaults nach type Options = die Abfrage beim Kompilieren ist und nie im JavaScript ankommt.

Die zwei Bedeutungen im Überblick

typeof zur Laufzeit (JavaScript)Type Query typeof (TypeScript)
Wo es stehtIn jedem Ausdruck: if, return, console.log(...)An Typpositionen: nach :, in type X = ..., in <...>
Beispieltypeof x === "string"let y: typeof x;
AusgewertetWenn der Code läuftVom Compiler, danach entfernt
ErgebnisEiner von acht StringsEin Typ
Verwendet fürEinen Wert prüfen und seinen Typ einengenEinen Typ aus einem Wert ableiten

TypeScript unterscheidet sie anhand der Position, dasselbe Schlüsselwort bedeutet also nie beides zugleich.

Ergebnisse von typeof zur Laufzeit

Der Laufzeitoperator gibt einen von acht Strings zurück. Führe das aus, um zu sehen, was gängige Werte ergeben:

WertErgebnis von typeofHinweis
"hi", Template-Strings"string"
42, 3.14, NaN, Infinity"number"NaN ist eine Zahl; prüfe es mit Number.isNaN
10n"bigint"
true, false"boolean"
undefined"undefined"
Symbol("id")"symbol"
Funktionen, Arrow Functions, Klassen"function"eine Klasse ist eine Konstruktorfunktion
null"object"ein historischer Bug in JavaScript
{}, [], new Date(), new Map()"object"Arrays sind Objekte: Array.isArray verwenden
new String("x"), new Number(1)"object"Wrapper-Objekte, meide sie

In reinem JavaScript gibt typeof auf eine nie deklarierte Variable "undefined" zurück, statt zu werfen. In TypeScript kompiliert dieser Code nicht: Cannot find name 'notDeclared'. (TS2304).

Narrowing mit typeof

Ein Vergleich mit typeof in einem if, einem switch, einem ternären Ausdruck oder nach && engt die Variable in jedem Zweig ein. Das ist der Standardweg, eine Union aus primitiven Typen zu behandeln, und die erste Prüfung, zu der du bei unknown greifst.

typeof x !== "string" engt in die andere Richtung ein, auf alles außer string. Und der Compiler kennt die acht möglichen Strings, ein Tippfehler im Vergleich ist also ein Compilerfehler statt einer Prüfung, die stillschweigend immer false ist:

Der Compiler gibt index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. aus. In reinem JavaScript würde der Tippfehler still jeden String in den Pfad für number schicken. Korrigiere die Schreibweise, dann gibt es text aus.

Die Falle typeof null

Weil typeof null gleich "object" ist, engt TypeScript typeof x === "object" auf object | null ein, nicht auf object. Ergänze eine Prüfung auf null, sonst ist das Lesen einer Eigenschaft ein Fehler (TS18047 'x' is possibly 'null'.).

typeof kann Arrays, Datumswerte, Maps und einfache Objekte nicht unterscheiden: Alle sind "object". Nimm Array.isArray(x) für Arrays und x instanceof Date für Klasseninstanzen. Weder typeof noch sonst etwas kann zur Laufzeit prüfen, ob ein Wert zu einem Interface passt, weil Interfaces entfernt werden; dafür schreibst du eine Type-Guard-Funktion, die die Eigenschaften prüft.

typeof an Typpositionen

In einem Typ kopiert typeof someVariable den Typ, den TypeScript für diese Variable abgeleitet hat (oder den du deklariert hast). Das erspart es, einen Typ für einen bereits vorhandenen Wert von Hand zu schreiben, und hält beide synchron.

const theme = {
  primary: "#3178c6",
  spacing: 8,
  dark: false,
};

type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }

function withSpacing(t: Theme, factor: number): Theme {
  return { ...t, spacing: t.spacing * factor };
}

console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }

let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed;    // "Ada" (a const keeps its literal type)

Das Ergebnis hängt davon ab, wie die Variable deklariert wurde: Eine let oder eine Objekteigenschaft wird erweitert (string, number), ein primitiver Wert in einer const behält seinen Literaltyp. Ergänze as const an einem Objekt oder Array, dann liefert typeof readonly Literaltypen bis ganz nach unten. typeof akzeptiert auch Eigenschaftszugriffe, typeof theme.spacing ist number, aber keine beliebigen Ausdrücke: type T = typeof getTheme(); ist ein Syntaxfehler (';' expected., TS1005). Für den Typ, den ein Aufruf zurückgibt, nimm ReturnType<typeof getTheme>.

keyof typeof: die Schlüssel eines Objekts als Typ

keyof typeof obj ist die häufigste Kombination. typeof macht das Objekt zu einem Typ, keyof nimmt dessen Schlüssel, und du bekommst eine Union der Eigenschaftsnamen, die korrekt bleibt, wenn sich das Objekt ändert.

Derselbe Trick auf einem konstanten Array liefert eine Union seiner Werte: Mit const roles = ["admin", "editor"] as const ist der Typ (typeof roles)[number] gleich "admin" | "editor". Ohne as const wäre er nur string. Die Seite zu keyof behandelt keyof für sich.

ReturnType<typeof fn> und typeof bei Klassen

Utility Types wie ReturnType und Parameters nehmen einen Funktionstyp. Ein Funktionsname ist ein Wert, also führst du ihn zuerst durch typeof:

ReturnType<createUser> zu schreiben scheitert mit 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), und genau darum bittet der Compiler hier.

Klassen sind der eine Fall, in dem ein Name zugleich Wert und Typ ist. User als Typ bedeutet eine Instanz der Klasse; typeof User bedeutet die Klasse selbst, den Konstruktor, einschließlich seiner statischen Member:

class Point {
  static origin = new Point(0, 0);
  constructor(public x: number, public y: number) {}
}

const p: Point = new Point(1, 2);        // an instance
const Ctor: typeof Point = Point;        // the class (constructor + statics)
const q = new Ctor(3, 4);                // q: Point
type Instance = InstanceType<typeof Point>; // Point

typeof vs instanceof vs ein Type Guard

PrüfungFunktioniert beiBeispiel
typeofprimitiven Werten, Funktionen, „ist es ein Objekt“typeof x === "number"
Array.isArrayArraysArray.isArray(x)
instanceofKlasseninstanzen (Date, Error, deine Klassen)x instanceof Date
inUnions aus Objekten, anhand einer Eigenschaft"email" in x
Type-Guard-FunktionInterfaces, Typaliasse, alles andereisUser(x) mit x is User

Nimm das engste Werkzeug, das passt. Für Klasseninstanzen siehe instanceof; für Formen, die ein Interface beschreibt, ist eine Type-Guard-Funktion die einzige verfügbare Prüfung zur Laufzeit.

Häufig gestellte Fragen

Was macht typeof in TypeScript?

Je nach Stelle zwei verschiedene Dinge. In einem Ausdruck ist typeof x der JavaScript-Operator: Er läuft und gibt einen String wie "string" oder "object" zurück, und TypeScript engt x ein, wenn du diesen String vergleichst. In einer Typannotation ist typeof x eine Type Query: Der Compiler wertet sie aus, sie liefert den statischen Typ der Variablen x und verschwindet aus dem erzeugten JavaScript.

Was ist keyof typeof in TypeScript?

keyof typeof obj liefert die Union der Eigenschaftsnamen eines Objekts als String-Literaltypen. typeof obj macht aus dem Objektwert seinen Typ, und keyof nimmt die Schlüssel dieses Typs. Für const colors = { red: "#f00", blue: "#00f" } ist keyof typeof colors gleich "red" | "blue".

Warum gibt typeof null "object" zurück?

Das ist ein Bug aus der ersten Version von JavaScript, der sich nicht mehr beheben lässt, ohne das Web kaputt zu machen. TypeScript bildet ihn ab: Nach typeof x === "object" ist x auf object | null eingeengt, du musst also zusätzlich x !== null prüfen, bevor du Eigenschaften liest.

Wie prüfe ich in TypeScript, ob ein Wert ein Array ist?

Mit Array.isArray(value), das auf einen Array-Typ einengt. typeof kann das nicht: typeof [] ist "object", genau wie bei einfachen Objekten und null.

Was ist der Unterschied zwischen typeof und instanceof in TypeScript?

typeof prüft die primitive Kategorie eines Werts ("string", "number", "function", "object"...). instanceof prüft, ob ein Objekt von einer bestimmten Klasse oder einem Konstruktor erzeugt wurde, etwa Date oder deine eigene class User. Nimm typeof für primitive Werte und instanceof für Klasseninstanzen; keins von beiden kann ein Interface oder einen Typalias prüfen, denn die existieren zur Laufzeit nicht.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S