Primitive Typen
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.
Inhaltsverzeichnis
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
: Textdatennumber
: Numerische Werte (Integer und Float)boolean
: Wahrheitswertenull
: Absichtlich leerer Wertundefined
: Nicht initialisierter Wertsymbol
: Eindeutige Identifierbigint
: Große Ganzzahlen
String
Der string
Typ repräsentiert Textdaten. In TypeScript gibt es wichtige Nuancen zu beachten.
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.
// 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.
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.
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.
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.
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.
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.
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MIN_SAFE_INTEGER);
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.
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
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).
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.
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));
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.
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);
}
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
.
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());
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.
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.
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.
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.
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
.
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.
// Methode 1
const isBoolean: boolean = Boolean(value);
// Methode 2
const isTruthy: boolean = !!value;
console.log(Boolean(0));
console.log(Boolean("Hallo"));
console.log(Boolean(""));
console.log(Boolean({}));
console.log(Boolean([]));
console.log(Boolean(-12));
false
true
false
true
true
true
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));
false
true
false
true
true
true
Boolean Literal Types
Die Boolean Literal Types werden folgendermaßen erstellt.
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.
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.
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.
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.
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.
// 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 auchnull
oderundefined
sein - Mit
strictNullChecks
: (Standard in modernen Projekten) Man muss explizit festelegen, wennnull
/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
.
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
.
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.
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.
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.
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
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
console.log(Number.MAX_SAFE_INTEGER + 1);
console.log(Number.MAX_SAFE_INTEGER + 2);
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.
// 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.
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.
const b1: bigint = BigInt(234898234098234989);
const b2: bigint = BigInt(989832987237987234);
const sum = b1 + b2;
console.log(sum);
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.
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);
}
123n
9000n
Cannot convert float to BigInt