TypeScript bietet ein robustes System von primitiven Datentypen, die als Grundbausteine für komplexere Typdefinitionen dienen. Diese primitiven Typen - string, number, boolean, null, undefined, symbol und bigint - erweitern die Typsicherheit von JavaScript durch präzise Deklarationen. Im Gegensatz zu JavaScript, wo Typen erst zur Laufzeit bestimmt werden, ermöglicht TypeScript die frühzeitige Typprüfung während der Entwicklung. Diese statische Typisierung verhindert häufige Fehler wie unbeabsichtigte Typkonvertierungen und verbessert die Code-Qualität sowie die Wartbarkeit. Die primitiven Typen bilden das Fundament des TypeScript-Ökosystems und sind essentiell für die Entwicklung zuverlässiger und skalierbarer Anwendungen mit klaren Datenstrukturen.

Einführung

Primitive Typen sind die fundamentalen Datentypen, aus denen alle komplexeren Strukturen aufgebaut werden. TypeScript kennt alle JavaScript-Primitives und bietet entsprechende Typen.

  • string: Textdaten
  • number: Numerische Werte (Integer und Float)
  • boolean: Wahrheitswerte
  • null: Absichtlich leerer Wert
  • undefined: Nicht initialisierter Wert
  • symbol: Eindeutige Identifier
  • bigint: Große Ganzzahlen

String

Der string Typ repräsentiert Textdaten. In TypeScript gibt es wichtige Nuancen zu beachten.

TypeScript Basis Verwendung
let message: string = "Hello TypeScript";
let name: string = 'John';
let template: string = `Welcome, ${name}`;

In TypeScript gibt es einen Unterschied zwischen string und String. In einem Fall handelt es sich um einen String und im anderen Fall ist es ein Objekt.

TypeScript string vs. String
// Primitiver String
let primitiveString: string = "Hello";

// String-Objekt
let objectString: String = new String("Hello");

Wichtig: Man sollte immer den primitiven String verwenden.

String Literal Types

Eine mächtige TypeScript Funktion ist die Möglichkeit, Strings auf spezifische Werte zu beschränken.

In diesem Beispiel dürfen nur angegebene String-Werte als Parameter der move() Funktion verwendet werden. Alle abweichenden Werte führen zu einem Fehler.

TypeScript Beispiel
type Direction = "north" | "south" | "east" | "west";

function move(direction: Direction) {
    console.log(`Moving ${direction}`);
}

move("north"); // OK
move("up"); // Error: Argument of type 'up' is not assignable

Ebenfalls gibt es die Möglichkeit, die Template Literal Types zu verwenden. Es sind im Grunde zusammengesetzte Typen, die TypeScript auf valide Werte prüfen kann.

TypeScript Beispiel
type EmailDomain = "gmail.com" | "outlook.com" | "company.com";
type Email = `${string}@${EmailDomain}`;

let validEmail: Email = "user@outlook.com"; // OK
let invalidEmail: Email = "user@yahoo.com"; // Fehler

String-Methoden und Type Safety

TypeScript kennt alle String-Methoden und kann sie entsprechend verwenden.

TypeScript Beispiel
function processText(input: string): string {
    return input
        .trim()
        .toLowerCase()
        .replace(/\s+/g, '-');
}

Man kann ebenfalls die sogenannten Type Guards einsetzen, um bestimmte Typen zu prüfen.

TypeScript Beispiel
function isValidEmail(value: unknown): value is string {
    return typeof value === "string" && value.includes("@");
}

Number

Der number Typ umfasst sowohl Ganzzahlen als auch Gleitkommazahlen. JavaScript (und damit auch TypeScript) verwenden IEEE 754 Gleitkomma-Arithmetik.

Hier ein Beispiel für die Verwendung verschiedener Datenformate.

TypeScript Zahlenformate
let numDecimal: number = 42;
let numFloat: number = 3.14159;
let numHex: number = 0xFF; // 255
let numBinary: number = 0b1010; // 10
let numOctal: number = 0o744; // 484
let numScientific: number = 1e5; // 100000

Die Klasse Number hat bestimmte Grenzen. Diese kann man wie folgt prüfen.

TypeScript Grenzen von Number
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MIN_SAFE_INTEGER);
Output
1.7976931348623157e+308
5e-324
9007199254740991
-9007199254740991

Häufige Fallen mit Zahlen

Gleitkomma-Präzision

Hier soll man an bestimmten bzw. bei bestimmten Kalkulationen Acht darauf geben, wie die Berechnung in Wirklichkeit aussieht.

JavaScript (und viele andere Programmiersprachen) verwenden das IEEE 754 Format für Fließkommazahlen (floating point). Dadurch können viele Dezimalzahlen nicht exakt dargestellt werden.

Ein klassisches Beispiel sieht man direkt hier.

TypeScript Beispiel
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
Output
0.30000000000000004
false

Wie man hier sieht, können numerische Vergleiche fehlschlagen und Rundungsfehler sich einschleichen. Besonders bei vielen Berechnungen oder im Finanzbereich.

✅ Mögliche Lösung: Toleranzbereich (Epsilon-Vergleich)

Da exakte Vergleiche problematisch sind, vergleicht man Zahlen, indem man prüft, ob sie “nah genug” beieinander liegen (also ihr Unterschied kleiner als ein definierter Schwellenwert ist).

TypeScript Beispiel - Lösung
Output
true
false

Integer-Prüfung

Im JavaScript-Number-Typ gibt es keinen eigenen Integer-Typ wie in vielen anderen Sprachen - alles ist number (intern 64 Bit float). Es ist oft wichtig zu wissen, ob eine Zahl wirklich eine Ganzzahl ist.

TypeScript Beispiel
function isInteger(value: number): boolean {
    return Number.isInteger(value);
}

console.log(isInteger(42));
console.log(isInteger(13.4));
console.log(isInteger("42"));
console.log(isInteger(NaN));
Output
true
false
false
false

Was macht Number.isInteger()?

  • Prüft, ob der Wert eine echte Ganzzahl ist (also ohne Nachkommastellen)
  • Prüft außerdem, ob der Typ wirklich eine number ist (nicht etwa ein String)

Sichere Arithmetik

Was ist mit “sicher” gemeint? Der Wertebereich von number ist begrenzt.

Es gibt Number.MAX_SAFE_INTEGER (2^53 - 1). Additionen oder Berechnungen können außerhalb dieses Bereichs landen (z.B. bei sehr großen Zahlen), dann sind Rundungsfehler oder gar Unendlichkeiten (Infinity) möglich.

TypeScript Beispiel
function safeAdd(a: number, b: number): number {
    const result = a + b;

    if (!Number.isFinite(result)) {
        throw new Error("Das Ergebnis ist nicht endlich");
    }

    if (Math.abs(result) > Number.MAX_SAFE_INTEGER) {
        throw new Error("Das Ergebnis geht über den sicheren Bereich hinaus");
    }

    return result;
}

console.log(safeAdd(10, 20));

try {
    console.log(safeAdd(Number.MAX_SAFE_INTEGER, 1));
} catch (error) {
    console.log(error.message);
}
Output
30
Das Ergebnis geht über den sicheren Bereich hinaus

Was passiert in diesem Beispiel genau?

  • Prüfung: Ist das Ergebnis endlich?: Falls nicht (Infinity, -Infinity, NaN), wird ein Fehler geworfen.
  • Prüfung: Ist das Ergebnis im sicheren Bereich?: Fall das Ergebnis größer als Number.MAX_SAFE_INTEGER ist, wird ebenfalls ein Fehler geworfen.

Numeric Literal Types

Numerisch Literaltypen erlauben es, einen Typ auf konkrete Zahlenwerte einzuschränken, anstatt auf alle Werte vom Typ number.

TypeScript Beispiel
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(): DiceRoll {
    return Math.floor(Math.random() * 6 + 1) as DiceRoll;
}

console.log(rollDice());
console.log(rollDice());
console.log(rollDice());
Output
5
3
4

Der Typ DiceRoll kann nur einen der Werte 1, 2, 3, 4, 5 oder 6 annehmen - nichts anderes. Das ist sogenannter Union Type - eine Vereinigung mehrerer konkreter Werte.

Branded Types

Ein Branded Type ist eine Technik, um primitive Typen (wie number) mit einer unsichtbaren “Markierung” (Brand) zu versehen. Diese Markierung gibt es nur auf Typ-Ebene - im JavaScript Code gibt es keine Auswirkung, aber der Compiler kann die Typen unterscheiden.

TypeScript Beispiel
type Age = number & { __brand: "age" };

Age ist ein Typ, der einerseits eine Zahl ist (number), aber zusätzlich das Brand-Tag { __brand: "age" } trägt. Das sorgt dafür, dass man einen Wert **nur als Age nutzen kann, wenn er vorher “gebranded” wurde.

Nun bauen wir das Beispiel etwas weiter aus und erzeugen eine Factory-Funktion zum Erzeugen eines Age-Wertes.

TypeScript Beispiel (Ausbau)
type Age = number & { __brand: "age" };

function createAge(value: number): Age {
    if (value < 0 || value > 150) {
        throw new Error("Invalid age");
    }

    return value as Age;
}

Hiermit haben wir Folgendes:

  • Nur wenn die Zahl im gültigen Bereich liegt, darf sie als Age verwendet werden.
  • as Age setzt das “Brand” auf Typ-Ebene.

Nun kann man jetzt verlangen, dass überall im Code, wo ein Age benötigt wird, nur von createAge() geprüfte Werte übergeben werden können.

Bauen wir das Beispiel noch ein wenig aus, um etwas Praxisbezug zu erhalten.

TypeScript Beispiel (Ausbau)
type Age = number & { __brand: "age" };

function createAge(value: number): Age {
    if (value < 0 || value > 150) {
        throw new Error("Invalid age");
    }

    return value as Age;
}

function registerPerson(name: string, age: Age) {
    console.log(`Person: ${name} hat einen gültigen Alter von ${age}`);
}

registerPerson("John", createAge(42));
registerPerson("Alice", 212);

Wenn wir diesen Code ausführen, erhalten wir einen Fehler, da 212 nicht über createAge() Funktion erzeugt/geprüft wurde.

TypeScript
error TS2345: Argument of type 'number' is not assignable to parameter of type 'Age'.
Type 'number' is not assignable to type '{ __brand: "age"; }'.

Boolean

Werte

Der boolean Typ ist der einfachste Primitive, aber seine korrekte Verwendung ist entscheidend.

Der boolean Typ kann nur zwei Werte annehmen: true oder false.

TypeScript
let isActive: boolean = true;
let isComplete: boolean = false;

Boolean vs. Truthy/Falsy

In JavaScript kann in Bedingungen (if, while, Ternary, …) jede beliebige Variable verwendet werden, nicht nur echte Booleans. Das führt dazu, dass nicht nur true oder false, sondern auch andere Werte als “wahr” oder “falsch” interpretiert werden.

Falsy Werte (in JavaScript) Folgende Werte gelten als falsch in Bedingungen:

  • false
  • 0 und -0
  • "" (leerer String)
  • null
  • undefined
  • NaN

Alle anderen Werte sind “truthy” (werden als wahr behandelt).

Werte können nach Boolean hauptsächlich auf zwei unterschiedliche Arten konvertiert werden.

TypeScript
// Methode 1
const isBoolean: boolean = Boolean(value);

// Methode 2
const isTruthy: boolean = !!value;
TypeScript Beispiel - Methode 1
console.log(Boolean(0));
console.log(Boolean("Hallo"));
console.log(Boolean(""));
console.log(Boolean({}));
console.log(Boolean([]));
console.log(Boolean(-12));
Output
false
true
false
true
true
true
TypeScript Beispiel - Methode 2
function checkTruthy(value: any): boolean {
    const result: boolean = !!value;
    return result;
}

console.log(checkTruthy(0));
console.log(checkTruthy("Hallo"));
console.log(checkTruthy(""));
console.log(checkTruthy({}));
console.log(checkTruthy([]));
console.log(checkTruthy(-12));
Output
false
true
false
true
true
true

Boolean Literal Types

Die Boolean Literal Types werden folgendermaßen erstellt.

TypeScript
type Yes = true;
type No = false;
type Answer = Yes | No;

Damit kann man einen Typ auf einen konkreten Wahrheitswert beschränken. Answer kann nur true oder false sein - das ist praktisch, wenn man z.B. Antworten, Status oder Enums modellieren möchte, die wirklich nur exakt diese Werte erlauben sollen.

TypeScript Beispiel (Ausbau)
type Yes = true;
type No = false;
type Answer = Yes | No;

let userAgreed: Answer = true; // Ok
userAgreed = false; // Ok
userAgreed = 1; // Fehler

Feature Flags

Hierbei handelt es sich eigentlich um bestimmte Interfaces, welche verschiedene Werte vom Typ boolean beinhalten und als eine Art Schalter wirken können.

TypeScript
interface FeatureFlags {
    darkMode: boolean;
    betaFeatures: boolean;
    debugMode: boolean;
}

Dieses Interface beschreibt ein Objekt mit verschiedenen Schaltern (Features), die aktiviert (true) oder deaktiviert (false) sein können. Solche Feature Flags werden oft zur Steuerung von Funktionen, Debugging oder Sonstigem eingesetzt.

Hier ein praktisches Beispiel.

TypeScript Beispiel
interface FeatureFlags {
    darkMode: boolean;
    betaFeatures: boolean;
    debugMode: boolean;
}

function initFeatures(debug: boolean = false): FeatureFlags {
    return {
        darkMode: localStorage.getItem("darkMode") === "true",
        betaFeatures: process.env.NODE_ENV === "development",
        debugMode: debug
    };
}

const features = initFeatures(true);

if (features.darkMode) { /* ... */ }
if (features.betaFeatures) { /* ... */ }
if (features.debugMode) { /* ... */ }

Null

JavaScript, und damit TypeScript, kennt zwei Arten von “Nichts”.

  • undefined: Das Fehlen eines Wertes (z.B. eine nicht initialisierte Variable, fehlender Rückgabewert, optionale Properties)
  • null: Ein explizit gesetzter “leer” Wert, um etwas absichtlich als “nichts” zu kennzeichnen

Beide sind primitive Datentypen.

TypeScript
let a: undefined = undefined;
let b: null = null;

Unterschiede

undefined

  • Wert existiert nicht
  • Für optionale Properties oder nicht initialisierte Variablen

null

  • Wert existiert, ist leer
  • Absichtlich “leer” gesetzt (z.B. keine Referenz mehr vorhanden)

TypeScript zwingt einen mit den richtigen Einstellungen (strictNullChecks) dazu, explizit zu entscheiden, ob ein Wert null und/oder undefined sein kann.

TypeScript Beispiel
// Muss undefined sein
let notInitialized: undefined = undefined;

// Muss null sein
let explicitNull: null = null;

// Kann string oder null sein
let value: string | null = null;

// Kann number oder undefined sein
let maybeNumber: number | undefined;

Ein Unterschied stellt die aktiviert Compiler-Option strictNullChecks dar.

  • Ohne strictNullChecks: Jede Variable kann automatisch auch null oder undefined sein
  • Mit strictNullChecks: (Standard in modernen Projekten) Man muss explizit festelegen, wenn null/undefined erlaubt ist - andernfalls ist ein Wert immer vorhanden.

Anwendungsfälle

Optionale Properties (undefined)

Im folgenden Beispiel ist email nicht verpflichtend. Fehlt es, ist der Wert undefined.

TypeScript
interface User {
    id: string;
    name: string;
    email?: string; // Kann string oder undefined sein
}

Nullable Fields (null)

In diesem Beispiel drückt deletedAt einen Zustand aus “wurde gelöscht”. Der Wert ist bewusst null.

TypeScript
interface User {
    id: string;
    name: string;
    deletedAt: Date | null; // Ein Datum oder explizit "gelöscht" (null)
}

Typ Guards (sichere Verarbeitung)

Wenn man Werte zulässt, die null oder undefined sein können, muss man prüfen, bevor man darauf zugreift.

TypeScript
function getLength(str: string | null | undefined): number {
    if (str === null || str === undefined) {
        return 0;
    }

    // Sicherer Zugriff, da jetzt garantiert string
    return str.length;
}

Symbol

Symbol ist ein primitiver Datentyp, der mit ES6 (ECMAScript 2015) eingeführt wurde.

Folgende Punkte machen ein Symbol aus:

  • Ein Symbol ist ein einzigartiger und unveränderlicher Wert
  • Es wird vor allem genutzt, um eindeutige Schlüssel für Objekte zu erstellen
  • Symbole sind nicht als Eigenschaftsnamen für “normale” Iteration sichtbar (z.B. for ... in, Object.keys())

Ein Symbol kann wie folgt erstellt werden.

TypeScript
const sym1 = Symbol("key");
const sym2 = Symbol("key");
console.log(sym1 === sym2); // false - Immer eindeutig

Symbol("key") erzeugt ein neues, eindeutiges Symbol. Der String "key" ist nur eine Beschreibung (zum Debuggen), nicht die Identität des Symbols. Auch wenn man zwei Symbole mit demselben Beschreibungstext erzeugt, sind sie immer unterschiedlich.

Globale Symbole

Es besteht auch die Möglichkeit, globale Symbole zu erzeugen. Dabei wird auf ein globales Symbol-Register zugegriffen.

Man bekommt immer dasselbe Symbol zurück für denselben Schlüssel.

Diese Art von Symbolen ist ideal, wenn man ein Symbol an mehreren Stellen im Programm/Modul wiederverwenden möchte.

TypeScript Globale Symbole
const globalSym1 = Symbol.for("app.key");
const globalSym2 = Symbol.for("app.key");

console.log(globalSym1 === globalSym2); // true

Wichtig

  • Mit Symbol() erzeugte Symbole sind nie gleich, auch bei gleichem Beschreibungstext.

  • Mit Symbol.for() erzeugte Symbole mit gleichem Schlüssel sind immer gleich. Warum Symbole für Properties?

  • Sie sind nicht versehentlich überschreibbar

  • Sie sind nicht sichtbar beim normalen Iterieren

  • Sie verhindern Namenskollisionen, z.B. in Libraries oder Frameworks

TypeScript Beispiel
class ApiClient {

    private [Symbol.toStringTag] = "ApiClient";
    private static readonly SECRET_KEY = Symbol("secretKey");

    private [ApiClient.SECRET_KEY]: string;

    constructor(secret: string) {
        this[ApiClient.SECRET_KEY] = secret;
    }

}

BigInt

BigInt wurde für Zahlen außerhalb des sicheren Integer-Bereiches eingeführt. JavaScript number kann nur ganzzahlige Werte im Bereich ±(2^53 - 1) exakt darstellen. Der Grenzwert ist die Konstante Number.MAX_SAFE_INTEGER (9007199254740991). Alles darüber hinaus kann zu Rundungsfehlern führen.

Das kann ein Problem in folgenden Fällen darstellen:

  • IDs und Counter mit vielen Ziffern
  • Sehr große Beträge (z.B. Cent-Beträge als Integer)
  • Zeitstempel mit hoher Präzision (Microsekunden)
  • Kryptografie
TypeScript Beispiel für Fehlerbereich
console.log(Number.MAX_SAFE_INTEGER + 1);
console.log(Number.MAX_SAFE_INTEGER + 2);
Output
9007199254740992
9007199254740992

Wie man an diesem Beispiel sieht, liefert die Addition mit zwei unterschiedlichen Werten dasselbe Ergebnis.

Seit ES2020 gibt es den Typ BigInt, der beliebig große Ganzzahlen exakt darstellen kann.

Folgendermaßen kann eine Zahl vom Typ BigInt erstellt werden.

TypeScript
// Via Funktion
const b1: bigint = BigInt(9007199254740992);

// Via 'n' Suffix (Literal)
const b2: bigint = 9007199254740993n;

Es besteht auch die Möglichkeit mithilfe des BigInt Konstruktors eine große Zahl aus einem String zu erstellen.

TypeScript
const b1: bigint = BigInt("87987234987234987234");

Operationen mit BigInt Typen sind nur zwischen BigInts erlaubt. Das Mischen von BigInt und Number führt zu einem TypeError.

TypeScript Beispiel für Addition
const b1: bigint = BigInt(234898234098234989);
const b2: bigint = BigInt(989832987237987234);
const sum = b1 + b2;
console.log(sum);
Output
1224731221336222176n

Konvertierung

Für Konvertierung von anderen Werten nach BigInt müssen ein paar Punkte beachtet werden.

  • Bei number unbedingt prüfen: Es darf nur eine Ganzzahl sein (kein Float)
  • Strings dürfen nur ganze Zahlen enthalten, sonst gibt es einen Fehler

Schauen wir uns ein praktisches Beispiel an.

TypeScript Beispiel - Konvertierung
function convertToBigInt(value: number | string): bigint {
    if (typeof value === "number") {
        if (!Number.isInteger(value)) {
            throw new Error("Cannot convert float to BigInt");
        }
    }

    return BigInt(value);
}

const r1 = convertToBigInt(123);
console.log(r1);

const r2 = convertToBigInt("9000");
console.log(r2);

try {
    const r3 = convertToBigInt(2.5);
} catch (error) {
    console.log(error.message);
}
Output
123n
9000n
Cannot convert float to BigInt
/ Weiter

Zurück zu TypeScript

Zur Übersicht