Object Utility Types
Object Utility Types in TypeScript wie Partial
, Required
, Record
und Pick
ermöglichen eine flexible und effiziente Manipulation von Objekttypen. Sie vereinfachen die Anpassung bestehender Typen, fördern die Wiederverwendbarkeit und erhöhen die Typsicherheit in komplexen Anwendungen. Durch den gezielten Einsatz dieser Utilities lassen sich Schnittstellen präzise modellieren und der Entwicklungsprozess deutlich optimieren.
Inhaltsverzeichnis
Einführung
TypeScript Object Utility Types sind vordefinierte, generische Typen, die es ermöglichen, neue Typen basierend auf bestehenden Objekttypen zu erstellen. Sie funktionieren wie “Werkzeuge” oder “Funktionen” auf Typebene, die bestehende Typen transformieren, modifizieren oder daraus neue Typen ableiten.
Warum Object Utility Types?
In der modernen JavaScript/TypeScript Entwicklung arbeitet man ständig mit komplexen Objekt-Strukturen. Öft benötigt man Variationen eines bestehenden Typs. Mal sollen alle Eigenschaften optional sein, mal nur bestimmte ausgewählt werden oder man möchte alle Eigenschaften als schreibgeschützt markieren. Ohne Object Utility Types müsste man diese Variationen manuell definieren.
Das würde zu Folgendem führen:
- Code-Duplikation
- Wartungsprobleme bei Änderungen des ursprünglichen Typs
- Inkonsistenzen zwischen verwandten Typen
- Erhöhter Aufwand bei der Typ-Definition
Mehrwert
Object Utility Types liefern also folgende kräftige Vorteile:
- Typ-Sicherheit: Sie stellen sicher, dass abgeleitete Typen automatisch konsistent bleiben, wenn sich der ursprüngliche Typ ändert.
- DRY-Prinzip: Einmal definierte Typen können in verschiedenen Variationen wiederverwendet werden.
- Wartbarkeit: Änderungen an Basis-Typen propagieren automatisch zu allen abgeleiteten Typen.
- Lesbarkeit: Der Code wird selbstdokumentierend, da die Intention durch den Utility Typ klar wird.
- Produktivität: Schnellere Entwicklung durch weniger manuelle Typ-Definition.
Object Utility Types
Partial<T>
- Alle Eigenschaften optional machen
Partial<T>
macht alle Eigenschaften eines Typs <T>
optional. Dies ist besonders nützlich, wenn man Updates oder partielle Daten verarbeiten muss.
Partial<T>
type Partial<T> = {
[P in keyof T]?: T[P]
};
Hier ein praktisches Beispiel.
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Normalen Benutzer erstellen
const user: User = {
id: 1,
name: "John",
email: "john@mail.com",
age: 30
};
// Neuen Benutzer auf Basis von 'User'
// mit weniger Eigenschaften erzeugen
const userPartial: Partial<User> = {
email: "new@mail.com"
};
// Ausgabe von beiden Objekten
console.log(user);
console.log(userPartial);
{ id: 1, name: 'John', email: 'john@mail.com', age: 30 }
{ email: 'new@mail.com' }
Hier ein weiteres Beispiel mit Verwendung in einer Funktion.
interface I_User {
id: number;
name: string;
email: string;
age: number;
active: boolean;
}
// Mit Partial einen Typ für Updates erstellen
type T_UserUpdate = Partial<I_User>;
// Ein Äquivalent zu T_UserUpdate (nur zum Zeigen)
type T_UserUpdateEquivalent = {
id?: number;
name?: string;
email?: string;
age?: string;
active?: boolean;
};
// User-Liste
const users: I_User[] = [
{
id: 1,
name: "John",
email: "john@mail.com",
age: 30,
active: true
},
{
id: 2,
name: "Tom",
email: "tom@mail.com",
age: 32,
active: false
},
{
id: 3,
name: "Alice",
email: "alice@mail.com",
age: 40,
active: true
}
];
// Hilfs-Funktion - Benutzer abrufen
function getUserById(userId: number): I_User | undefined {
return users.find(u => u.id === userId);
}
// Funktion zur Aktualisierung der Benutzer
function updateUser(userId: number, updates: T_UserUpdate): I_User {
// Hier kann man sicher sein, dass nur
// gültige User-Eigenschaften übergeben werden, aber
// sind alle optional
const existingUser = getUserById(userId);
return {
...existingUser,
...updates
};
}
// Verwendung
console.log(updateUser(1, { name: "Max" }));
console.log(updateUser(1, { email: "max@mail.com", age: 31 }));
console.log(updateUser(2, { active: true }));
console.log(updateUser(3, {}));
{ id: 1, name: 'Max', email: 'john@mail.com', age: 30, active: true }
{ id: 1, name: 'John', email: 'max@mail.com', age: 31, active: true }
{ id: 2, name: 'Tom', email: 'tom@mail.com', age: 34, active: true }
{
id: 3,
name: 'Alice',
email: 'alice@mail.com',
age: 40,
active: true
}
Required<T>
- Alle Eigenschaften verpflichtend machen
Required<T>
ist das Gegenteil von Partial<T>
und macht alle Eigenschaften eines Typs verpflichtend, auch wenn sie ursprünglich optional waren.
Required<T>
type Required<T> = {
[P in keyof T]-?: T[P]
};
Nun schauen wir uns die Funktionsweise an einem praktischen Beispiel an.
interface I_ApiConfig {
baseUrl?: string;
timeout?: number;
retries?: number;
headers?: Record<string, string>;
}
// Finale Konfiguration soll alle Werte haben
type T_ApiConfigFull = Required<I_ApiConfig>;
// Äquivalent zu T_ApiConfigFull (nur zum Zeigen)
type T_ApiConfigFullEquivalent = {
baseUrl: string;
timeout: number;
retries: number;
headers: Record<string, string>;
};
class ApiConfigBuilder {
private config: Partial<I_ApiConfig> = {};
setBaseUrl(url: string): this {
this.config.baseUrl = url;
return this;
}
setTimeout(ms: number): this {
this.config.timeout = ms;
return this;
}
setRetries(count: number): this {
this.config.retries = count;
return this;
}
setHeaders(headers: Record<string, string>): this {
this.config.headers = headers;
return this;
}
build(): Required<I_ApiConfig> {
if (!this.config.baseUrl) throw new Error("baseUrl is required");
if (this.config.timeout === undefined) throw new Error("timeout is required");
if (this.config.retries === undefined) throw new Error("retries is required");
if (!this.config.headers) throw new Error("headers is required");
return this.config as T_ApiConfigFull;
}
}
// Verwendung
const configOne = new ApiConfigBuilder()
.setBaseUrl("http://localhost:9000")
.setTimeout(5000)
.setRetries(3)
.setHeaders({ "Content-Type": "application/json" })
.build();
console.log(configOne);
try {
const configTwo = new ApiConfigBuilder()
.setBaseUrl("http://localhost:8000")
.setRetries(2)
.setHeaders({ "Content-Type": "application/json" })
.build();
} catch (error) {
console.log("Fehler:", error.message);
}
{
baseUrl: 'http://localhost:9000',
timeout: 5000,
retries: 3,
headers: { 'Content-Type': 'application/json' }
}
Fehler: timeout is required
In diesem Beispiel haben wir zwei Objekt vom Typ T_ApiConfigFull
definiert. Im ersten Objekt sind alle Felder vorhanden. Im zweiten Objekt wurde ein Feld nicht übergeben/gesetzt. Dies führt zu einem Fehler, das die Methode build()
vom Typ T_ApiConfigFull
(was gleich Required<I_ApiConfig>
ist) zurückgibt und vorher das Vorhandensein aller Felder prüft.
Schauen wir uns ein weiteres Beispiel an.
interface I_CreateUserInput {
name?: string;
email?: string;
password?: string;
}
/**
* Create user
* ---
* Nach Validierung und Verarbeitung
* sind alle Felder vorhanden.
* ---
* @returns {Required<I_CreateUserInput>}
*/
function createUser(input: I_CreateUserInput): Required<I_CreateUserInput> {
if (!input.name) throw new Error("Name is required");
if (!input.email) throw new Error("Email is required");
if (!input.password) throw new Error("Password is required");
return input as Required<I_CreateUserInput>;
}
const userOne = createUser({
name: "John",
email: "john@mail.com",
password: "12345"
});
console.log(userOne);
try {
const userTwo = createUser({
name: "Tom",
email: "tom@mail.com"
});
} catch (error) {
console.log("Fehler:", error.message);
}
{ name: 'John', email: 'john@mail.com', password: '12345' }
Fehler: Password is required
In diesem Beispiel erwartet die Funktion createUser()
ein Objekt mit allen Feldern, welche im Interface I_CreateUserInput
definiert sind. Andernfalls, wie im Fall von userTwo
, wird ein Fehler geworfen.
Readonly<T>
- Alle Eigenschaften schreibgeschützt machen
Readonly<T>
macht alle Eigenschaften eines Typs schreibgeschützt. Dies ist nützlich für unveränderliche Daten-Strukturen oder um versehentliche Modifikationen zu verhindern.
Readonly<T>
type Readonly<T> = {
readonly [P in keyof T]: T[P]
};
Beispiel 1
Schauen wir uns ein praktisches Beispiel an, das die Verwendung von Readonly<T>
verdeutlicht.
interface I_Product {
id: number;
name: string;
price: number;
category: string;
}
// Schreibgeschützte Version
type T_ProductReadonly = Readonly<I_Product>;
// Äquivalent zu T_ProductReadonly (als Beispiel)
type T_ProductReadonlyEquivalent = {
readonly id: number;
readonly name: string;
readonly price: number;
readonly category: string;
};
/**
* Fetch product
* ---
* @param {number} id - ID of the product
* ---
* @returns {T_ProductReadonly}
*/
function fetchProduct(id: number): T_ProductReadonly {
const product = {
id,
name: "Laptop",
price: 1099,
category: "Electronis"
};
return product;
}
const product = fetchProduct(1);
console.log(product);
// Versuch etwas zu ändern
// product.price = 999;
// Cannot assign to 'price' because it is a read-only property.
{ id: 1, name: 'Laptop', price: 1099, category: 'Electronis' }
Beispiel 2
Lasst uns ein weiteres, umfangreicheres Beispiel aufbauen und Readonly<T>
verwenden.
interface I_Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
interface I_User {
id: string;
name: string;
email: string;
}
interface I_CartItem {
productId: string;
quantity: number;
}
interface I_AppState {
user: I_User | null;
products: I_Product[];
cart: I_CartItem[];
isLoading: boolean;
}
// Schreibgeschützte Version
type T_AppStateReadonly = Readonly<I_AppState>;
class Store {
private state: I_AppState = {
user: null,
products: [],
cart: [],
isLoading: false
};
/**
* Get current state
* ---
* @returns {T_AppStateReadonly}
*/
getState(): T_AppStateReadonly {
return this.state;
}
/**
* Set state
* ---
* @param newState - New state
*/
private setState(newState: Partial<I_AppState>): void {
this.state = { ...this.state, ...newState };
}
/**
* Set loading status
* ---
* @param {boolean} loading - New loading status
*/
setLoading(loading: boolean): void {
this.setState({ isLoading: loading });
}
/**
* Login user
* ---
* @param {I_User} user - User data
*/
loginUser(user: I_User): void {
this.setState({ user, isLoading: false });
}
/**
* Logout user
*/
logoutUser(): void {
this.setState({ user: null, cart: [] });
}
/**
* Load products
* ---
* @param {I_Product[]} products - Products to load
*/
loadProducts(products: I_Product[]): void {
this.setState({ products, isLoading: false });
}
/**
* Add to cart
* ---
* @param {string} productId - ID of the product
* @param {number} quantity - Quantity to add
*/
addToCart(productId: string, quantity: number = 1): void {
const existingItem = this.state.cart.find(i => i.productId === productId);
const newCart = existingItem
? this.state.cart.map(item =>
item.productId === productId
? { ...item, quantity: item.quantity + quantity }
: item
)
: [...this.state.cart, { productId, quantity }];
this.setState({ cart: newCart });
}
/**
* Remove from cart
* ---
* @param {string} productId - ID of the product to remove
*/
removeFromCart(productId: string): void {
const newCart = this.state.cart.filter(i => i.productId !== productId);
this.setState({ cart: newCart });
}
}
const store = new Store();
// Initialer Zustand
console.log("Initialer Zustand:", store.getState());
console.log("\n--- --- ---\n");
// Benutzer anmelden
store.setLoading(true);
store.loginUser({
id: "u_001",
name: "John",
email: "john@mail.com"
});
console.log("Nach Login:", store.getState());
console.log("\n--- --- ---\n");
// Produkte laden
store.setLoading(true);
store.loadProducts([
{ id: "p_001", name: "Laptop", price: 999, inStock: true },
{ id: "p_002", name: "Smartphone", price: 699, inStock: true },
{ id: "p_003", name: "Headphones", price: 149, inStock: false }
]);
console.log("Produkte geladen:", store.getState());
console.log("\n--- --- ---\n");
// Warenkorb Aktionen
store.addToCart("p_001");
store.addToCart("p_002", 2);
console.log("Im Warenkorb:", store.getState());
console.log("\n--- --- ---\n");
store.removeFromCart("p_002");
console.log("Neuer Warenkorb:", store.getState());
console.log("\n--- --- ---\n");
// Benutzer abmelden
store.logoutUser();
console.log("Nach Logout:", store.getState());
Initialer Zustand: { user: null, products: [], cart: [], isLoading: false }
--- --- ---
Nach Login: {
user: { id: 'u_001', name: 'John', email: 'john@mail.com' },
products: [],
cart: [],
isLoading: false
}
--- --- ---
Produkte geladen: {
user: { id: 'u_001', name: 'John', email: 'john@mail.com' },
products: [
{ id: 'p_001', name: 'Laptop', price: 999, inStock: true },
{ id: 'p_002', name: 'Smartphone', price: 699, inStock: true },
{ id: 'p_003', name: 'Headphones', price: 149, inStock: false }
],
cart: [],
isLoading: false
}
--- --- ---
Im Warenkorb: {
user: { id: 'u_001', name: 'John', email: 'john@mail.com' },
products: [
{ id: 'p_001', name: 'Laptop', price: 999, inStock: true },
{ id: 'p_002', name: 'Smartphone', price: 699, inStock: true },
{ id: 'p_003', name: 'Headphones', price: 149, inStock: false }
],
cart: [
{ productId: 'p_001', quantity: 1 },
{ productId: 'p_002', quantity: 2 }
],
isLoading: false
}
--- --- ---
Neuer Warenkorb: {
user: { id: 'u_001', name: 'John', email: 'john@mail.com' },
products: [
{ id: 'p_001', name: 'Laptop', price: 999, inStock: true },
{ id: 'p_002', name: 'Smartphone', price: 699, inStock: true },
{ id: 'p_003', name: 'Headphones', price: 149, inStock: false }
],
cart: [ { productId: 'p_001', quantity: 1 } ],
isLoading: false
}
--- --- ---
Nach Logout: {
user: null,
products: [
{ id: 'p_001', name: 'Laptop', price: 999, inStock: true },
{ id: 'p_002', name: 'Smartphone', price: 699, inStock: true },
{ id: 'p_003', name: 'Headphones', price: 149, inStock: false }
],
cart: [],
isLoading: false
}
Beispiel 3
Im nächten Beispiel bauen wir eine kleine Funktion zum Herstellen der Datenbank-Verbindung auf. Dies ist lediglich eine vereinfachte Version, die ebenfalls die Verwendung von Readonly<T>
demonstriert.
interface I_DatabaseConfig {
host: string;
port: number;
database: string;
ssl: boolean;
}
/**
* Create database connection
* ---
* @param {Readonly<I_DatabaseConfig>} config - Database configuration
*/
function createDatabaseConnection(config: Readonly<I_DatabaseConfig>) {
console.log(`Connecting to: ${config.host}:${config.port}`);
// Für Modifikation expliti ein neues Objekt erstellen
const newConfig = { ...config, port: 3007 };
return newConfig;
}
const conn = createDatabaseConnection({ host: "localhost", port: 3306, database: "my_db", ssl: true });
console.log(conn);
Connecting to: localhost:3306
{ host: 'localhost', port: 3007, database: 'my_db', ssl: true }
Pick<T, K>
- Bestimmte Eigenschaften auswählen
Pick<T, K>
erstellt einen neuen Typ, indem es nur die angegebenen Eigenschaften K
aus dem Typ T
auswählt.
Pick<T, K extends keyof T>
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
};
Beispiel 1
Zuerst werfen wir einen Blick auf ein einfaches Beispiel.
interface I_User {
id: number;
username: string;
email: string;
salary: number;
}
type T_UserLight = Pick<I_User, "id" | "username">;
const userFull: I_User = {
id: 1,
username: "john",
email: "john@mail.com",
salary: 50000
};
const userLight: T_UserLight = {
id: 2,
username: "tom"
};
console.log(userFull);
console.log("--- --- ---")
console.log(userLight);
{ id: 1, username: 'john', email: 'john@mail.com', salary: 50000 }
--- --- ---
{ id: 2, username: 'tom' }
In diesem Beispiel haben wir zwei Objekte (Benutzer). In einem Fall haben wir einen Benutzer vom Typ I_User
definiert. Dieses Objekt muss das gesamte Interface und entsprechend auch alle Felder implementieren. Im zweiten Fall definieren wir einen Benutzer vom Typ T_UserLight
. Hier müssen lediglich die beiden definierten Felder implementiert (vorhanden) sein.
Beispiel 2
Schauen wir uns die Funktionsweise anhand eines komplexeren Beispiels an. Wie bereits erwähnt, ermöglicht Pick<T, K>
nur bestimmte Felder eines bereits vorhandenen Typs auszuwählen.
interface I_User {
id: number;
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
dateOfBirth: Date;
address: string;
phoneNumber: string;
createdAt: Date;
updatedAt: Date;
isActive: boolean;
role: "admin" | "moderator" | "user";
}
// Nur für Login benötigte Felder
type T_LoginCredentials = Pick<I_User, "username" | "password">;
// Äquivalent zu T_LoginCredentials (nur als Beispiel)
type T_LoginCredentialsEquivalent = {
username: string;
password: string;
};
// Für öffentliche Profile (ohne sensitive Daten)
type T_PublicProfile = Pick<I_User, "id" | "username" | "firstName" | "lastName">;
// Für Benutzer-Liste im Admin-Panel
type T_UserListItem = Pick<I_User, "id" | "username" | "email" | "isActive" | "role">;
// Mock-Datenbank
const usersDatabase: I_User[] = [
{
id: 1,
username: "john",
email: "john@example.com",
password: "secure123",
firstName: "John",
lastName: "Doe",
dateOfBirth: new Date("1990-01-01"),
address: "123 Main St",
phoneNumber: "555-1234",
createdAt: new Date("2020-01-01"),
updatedAt: new Date("2023-01-01"),
isActive: true,
role: "user"
},
{
id: 2,
username: "admin",
email: "admin@example.com",
password: "admin123",
firstName: "Admin",
lastName: "User",
dateOfBirth: new Date("1985-05-15"),
address: "456 Admin Ave",
phoneNumber: "555-5678",
createdAt: new Date("2019-01-01"),
updatedAt: new Date("2023-06-01"),
isActive: true,
role: "admin"
}
];
/**
* Authenticate user
* ---
* @param {string} username - Username
* @param {string} password - Password
* ---
* @returns {Promise<string>}
*/
async function authenticateUser(username: string, password: string): Promise<string> {
const user = usersDatabase.find(u => u.username === username && u.password === password);
if (!user) throw new Error("Invalid credentials");
return `token-for-${user.id}`;
}
/**
* Get user by ID
* ---
* @param {number} userId - ID of the user
* ---
* @returns {I_User}
*/
function getUserById(userId: number): I_User {
const user = usersDatabase.find(u => u.id === userId);
if (!user) throw new Error("User not found");
return user;
}
/**
* Login
* ---
* @param {T_LoginCredentials} credentials - Login credentials
* ---
* @returns {Promise<string>}
*/
async function login(credentials: T_LoginCredentials): Promise<string> {
// Hier sind nur username und password verfügbar
return authenticateUser(credentials.username, credentials.password);
}
/**
* Get user profile
* ---
* @param {number} userId - ID of the user
* ---
* @returns {T_PublicProfile}
*/
function getUserProfile(userId: number): T_PublicProfile {
const user = getUserById(userId);
// Explizite Auswahl verhindert versehentliche Preisgabe von Daten
return {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName
};
}
/**
* Get active users
* ---
* @returns {T_UserListItem[]}
*/
function getActiveUsers(): T_UserListItem[] {
return usersDatabase
.filter(u => u.isActive)
.map(u => ({
id: u.id,
username: u.username,
email: u.email,
isActive: u.isActive,
role: u.role
}));
}
// Beispiel - Ausführung
async function runExample() {
try {
// Login (Beispiel)
const token = await login({
username: "john",
password: "secure123"
});
console.log("Token:", token);
console.log("\n--- --- ---\n");
// Profil abrufen (Beispiel)
const profile = getUserProfile(1);
console.log("User profile:", profile);
console.log("\n--- --- ---\n");
// Admin-Liste (Beispiel)
const activeUsers = getActiveUsers();
console.log("Active users:", activeUsers);
} catch (error) {
if (error instanceof Error) {
console.log("Fehler:", error.message);
} else {
console.log("Fehler:", error);
}
}
}
runExample();
--- --- ---
User profile: { id: 1, username: 'john', firstName: 'John', lastName: 'Doe' }
--- --- ---
Active users: [
{
id: 1,
username: 'john',
email: 'john@example.com',
isActive: true,
role: 'user'
},
{
id: 2,
username: 'admin',
email: 'admin@example.com',
isActive: true,
role: 'admin'
}
]
Beispiel 3
In diesem Beispiel wird wieder ein Basis-Interfaces definiert. Daraus werden unterschiedliche Typen gebildet, welche nur einen Ausschnitt an Eigenschaften des Basis-Interfaces führen und für unterschiedliche Zwecke verwendet werden.
interface I_ApiProduct {
id: number;
sku: string;
name: string;
description: string;
price: number;
cost: number;
inventory: number;
supplierId: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
// Für öffentliche API - ohne interne Informationen
type T_PublicProduct = Pick<I_ApiProduct, "id" | "name" | "description" | "price" | "isActive">;
// Für Inventar-Management
type T_InventoryItem = Pick<I_ApiProduct, "id" | "sku" | "name" | "inventory" | "isActive">;
// Für Einkauf
type T_PurchaseItem = Pick<I_ApiProduct, "id" | "name" | "cost" | "supplierId" | "inventory">;
// Mock-Datenbank
const productsDatabase: I_ApiProduct[] = [
{
id: 1,
sku: "prod_001",
name: "Premium Kopfhörer",
description: "Noise-Cancelling Köpfhörer mit exzellentem Klang",
price: 299.99,
cost: 120.50,
inventory: 150,
supplierId: 101,
isActive: true,
createdAt: new Date("2025-06-21"),
updatedAt: new Date("2025-06-22")
},
{
id: 2,
sku: "prod_002",
name: "Mechanische Tastatur",
description: "RGB-beleuchtete mechanische Tastatur",
price: 129.99,
cost: 45.80,
inventory: 75,
supplierId: 102,
isActive: true,
createdAt: new Date("2025-06-21"),
updatedAt: new Date("2025-06-22")
},
{
id: 3,
sku: "prod_003",
name: "Veraltetes Produkt",
description: "Dieses Produkt wird nicht mehr verkauft",
price: 49.99,
cost: 20.00,
inventory: 10,
supplierId: 103,
isActive: false,
createdAt: new Date("2022-11-05"),
updatedAt: new Date("2025-06-20")
}
];
/**
* Get all products
* ---
* @returns {I_ApiProduct[]}
*/
function getAllProducts(): I_ApiProduct[] {
return productsDatabase;
}
/**
* Get public products
* ---
* @returns {T_PublicProduct[]}
*/
function getPublicProducts(): T_PublicProduct[] {
const allProducts = getAllProducts();
return allProducts.map(product => ({
id: product.id,
name: product.name,
description: product.description,
price: product.price,
isActive: product.isActive
}));
}
/**
* Get inventory items
* ---
* @returns {T_InventoryItem[]}
*/
function getInventoryItems(): T_InventoryItem[] {
const allProducts = getAllProducts();
return allProducts.map(product => ({
id: product.id,
sku: product.sku,
name: product.name,
inventory: product.inventory,
isActive: product.isActive
}));
}
/**
* Get purchase items
* ---
* @returns {T_PurchaseItem[]}
*/
function getPurchaseItems(): T_PurchaseItem[] {
const allProducts = getAllProducts();
return allProducts
.filter(product => product.inventory < 100)
.map(product => ({
id: product.id,
name: product.name,
cost: product.cost,
supplierId: product.supplierId,
inventory: product.inventory
}));
}
// Ausführung
function runExample() {
try {
// Öffentliche Produkte abrufen
const publicProducts = getPublicProducts();
console.log("Öffentliche Produkte");
console.log(publicProducts);
console.log("--- --- ---");
// Inventar-Liste abrufen
const inventoryItems = getInventoryItems();
console.log("Inventar-Liste");
console.log(inventoryItems);
console.log("--- --- ---");
// Einkaufsliste abrufen
const purchaseItems = getPurchaseItems();
console.log("Einkaufsliste");
console.log(purchaseItems);
} catch (error) {
if (error instanceof Error) {
console.log("Fehler:", error.message);
} else {
console.log("Fehler:", error);
}
}
}
runExample();
Öffentliche Produkte
[
{
id: 1,
name: 'Premium Kopfhörer',
description: 'Noise-Cancelling Köpfhörer mit exzellentem Klang',
price: 299.99,
isActive: true
},
{
id: 2,
name: 'Mechanische Tastatur',
description: 'RGB-beleuchtete mechanische Tastatur',
price: 129.99,
isActive: true
},
{
id: 3,
name: 'Veraltetes Produkt',
description: 'Dieses Produkt wird nicht mehr verkauft',
price: 49.99,
isActive: false
}
]
--- --- ---
Inventar-Liste
[
{
id: 1,
sku: 'prod_001',
name: 'Premium Kopfhörer',
inventory: 150,
isActive: true
},
{
id: 2,
sku: 'prod_002',
name: 'Mechanische Tastatur',
inventory: 75,
isActive: true
},
{
id: 3,
sku: 'prod_003',
name: 'Veraltetes Produkt',
inventory: 10,
isActive: false
}
]
--- --- ---
Einkaufsliste
[
{
id: 2,
name: 'Mechanische Tastatur',
cost: 45.8,
supplierId: 102,
inventory: 75
},
{
id: 3,
name: 'Veraltetes Produkt',
cost: 20,
supplierId: 103,
inventory: 10
}
]
Beispiel 4
Im letzten Beispiel zu Pick
bauen wir eine Mini-Anwendung zur Erstellung neuer Benutzer. Auch in diesem Beispiel gibt es ein Basis-Interface (I_User
). Davon wird mithilfe von Pick
ein Ausschnitt an Eigenschaften verwendet, um einen Ableger-Typ zu definieren.
Es wird ein schlankeres Interface I_UserCreate
erstellt.
interface I_User {
id: number;
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
dateOfBirth: Date;
address: string;
phoneNumber: string;
createdAt: Date;
updatedAt: Date;
isActive: boolean;
role: "admin" | "moderator" | "user";
}
interface I_UserCreate {
username: string;
email: string;
password: string;
passwordConfirm: string;
firstName: string;
lastName: string;
agreedToTerms: boolean;
}
// Für Backend ohne UI-spezifische Felder
type T_UserCreateDto = Pick<I_UserCreate, "username" | "email" | "password" | "firstName" | "lastName">;
// Mock-Datenbank
let usersDatabase: I_User[] = [
{
id: 1,
username: "existing_user",
email: "existing@mail.com",
password: "hashedPassword1",
firstName: "Existing",
lastName: "User",
dateOfBirth: new Date("1990-01-01"),
address: "123 Main St",
phoneNumber: "555-1234",
createdAt: new Date("2025-06-25"),
updatedAt: new Date("2025-06-25"),
isActive: true,
role: "user"
}
];
/**
* Save user to database
* ---
* @param {T_UserCreateDto} userData - New user data
* ---
* @returns {Promise<I_User>}
*/
async function saveUserToDatabase(userData: T_UserCreateDto): Promise<I_User> {
// Prüfe username
if (usersDatabase.some(u => u.username === userData.username)) {
throw new Error("Username already exists");
}
// Prüfe email
if (usersDatabase.some(u => u.email === userData.email)) {
throw new Error("Email already exists");
}
// Neuen Benutzer erstellen (mit Standardwerten)
const newUser: I_User = {
id: usersDatabase.length + 1,
...userData,
dateOfBirth: new Date(),
address: "",
phoneNumber: "",
createdAt: new Date(),
updatedAt: new Date(),
isActive: true,
role: "user"
};
usersDatabase.push(newUser);
return newUser;
}
/**
* Validate user form
* ---
* @param {I_UserCreate} formData - Form data
* ---
* @returns {string[]}
*/
function validateUserForm(formData: I_UserCreate): string[] {
const errors: string[] = [];
if (!formData.username) errors.push("Username is required");
if (formData.username.length < 3) errors.push("Username must be at least 3 characters");
if (!formData.email.includes("@")) errors.push("Invalid email format");
if (formData.password.length < 6) errors.push("Password must be at least 6 characters");
if (formData.password !== formData.passwordConfirm) errors.push("Passwords do not match");
if (!formData.firstName) errors.push("Firstname is required");
if (!formData.agreedToTerms) errors.push("You must agree to the terms");
return errors;
}
/**
* Create user
* ---
* @param {T_UserCreateDto} userData - User data
* ---
* @returns {Promise<I_User>}
*/
async function createUser(userData: T_UserCreateDto): Promise<I_User> {
return saveUserToDatabase(userData);
}
async function runExample() {
try {
// Simuliere Formular-Eingabe
const formData: I_UserCreate = {
username: "new_user",
email: "new@mail.com",
password: "hashedPassword2",
passwordConfirm: "hashedPassword2",
firstName: "New",
lastName: "User",
agreedToTerms: true
};
// Validiere Formular
const errors = validateUserForm(formData);
if (errors.length > 0) {
console.log("Formular-Fehler:", errors);
return;
}
// Für Backend-Request
const userDto: T_UserCreateDto = {
username: formData.username,
email: formData.email,
password: formData.password,
firstName: formData.firstName,
lastName: formData.lastName
};
console.log("Creating user:", userDto);
// Benutzer erstellen
const createdUser = await createUser(userDto);
console.log("Benutzer erfolgreich erstellt:", {
id: createdUser.id,
username: createdUser.username,
email: createdUser.email
});
// Alle Benutzer auslesen
console.log("Alle Benutzer:", usersDatabase.map(u => ({
id: u.id,
username: u.username,
email: u.email
})));
} catch (error) {
console.log("Fehler:", error instanceof Error ? error.message : error);
}
}
runExample();
Erstelle Benutzer: {
username: 'new_user',
email: 'new@mail.com',
password: 'hashedPassword2',
firstName: 'New',
lastName: 'User'
}
Benutzer erfolgreich erstellt: { id: 2, username: 'new_user', email: 'new@mail.com' }
Alle Benutzer: [
{ id: 1, username: 'existing_user', email: 'existing@mail.com' },
{ id: 2, username: 'new_user', email: 'new@mail.com' }
]
Omit<T, K>
- Bestimmte Eigenschaften ausschließen
Omit<T, K>
ist das Gegenteil von Pick<T, K>
und erstellt einen neuen Typ, indem es die angegebenen Eigenschaften K
aus dem Typ T
entfernt.
Omit<T, K extends keyof T>
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Beispiel 1
Zuerst schauen wir uns ein minimales Beispiel an, um zu verstehen, wie Omit
funktioniert. Im ersten Beispiel definieren wir zwei Benutzer-Typen. Einen Basis-Typ und einen angeleiteten Typen auf Basis vom Basis-Typ mit ein paar entfernten Eigenschaften.
interface I_User {
id: number;
username: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
role: "admin" | "user";
}
type T_UserReduced = Omit<I_User, "id" | "password" | "updatedAt">;
const userFull: I_User = {
id: 1,
username: "John",
email: "john@mail.com",
password: "john123",
createdAt: new Date(),
updatedAt: new Date(),
role: "admin"
};
const userLight: T_UserReduced = {
username: "tom",
email: "tom@mail.com",
createdAt: new Date(),
role: "user"
};
console.log(userFull);
console.log(userLight);
{
id: 1,
username: 'John',
email: 'john@mail.com',
password: 'john123',
createdAt: 2025-06-25T09:55:31.660Z,
updatedAt: 2025-06-25T09:55:31.660Z,
role: 'admin'
}
{
username: 'tom',
email: 'tom@mail.com',
createdAt: 2025-06-25T09:55:31.660Z,
role: 'user'
}
Wenn wir versuchen würden, beim reduzierten Typ des Benutzers eine Eigenschaft hinzuzufügen, die vorher mit Omit
ausgeschlossen wurde, erhalten wir einen Fehler.
interface I_User {
id: number;
username: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
role: "admin" | "user";
}
type T_UserReduced = Omit<I_User, "id" | "password" | "updatedAt">;
// ❌ error TS2353: Object literal may only specify known properties, and 'id' does not exist in type 'T_UserReduced'
const user: T_UserReduced = {
id: 3,
username: "alice",
email: "alice@mail.com",
createdAt: new Date(),
role: "user"
};
Beispiel 2
Im zweiten Beispiel definieren wir ein Basis-Interface für Benutzer (U_User
). Dieses Interface beinhaltet alle Eigenschaften, die je für unterschiedliche Kontexte benötigt werden.
Von diesem Basis-Typ werden weitere Typen definiert, die für die jeweiligen Fälle verwendet werden. Damit werden einige Eigenschaften entfernt.
interface I_User {
id: number;
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
dateOfBirth: Date;
address: string;
phoneNumber: string;
createdAt: Date;
updatedAt: Date;
isActive: boolean;
role: "admin" | "moderator" | "user";
}
// Für User-Updates: Alles, außer den vom System verwalteten Feldern
type T_UserUpdateInput = Omit<I_User, "id" | "createdAt" | "updatedAt">;
// Für User-Erstellung: Ohne ID und Timestamps
type T_UserCreateInput = Omit<I_User, "id" | "createdAt" | "updatedAt">;
// Für öffentliche Anzeige: Ohne sensitive Daten
type T_UserSafe = Omit<I_User, "password">;
// Mock-Datenbank
let usersDatabase: I_User[] = [
{
id: 1,
username: "admin",
email: "admin@mail.com",
password: "hashedPassword1",
firstName: "Admin",
lastName: "User",
dateOfBirth: new Date("1985-03-01"),
address: "Neue Straße 2",
phoneNumber: "0923 23123 45",
createdAt: new Date("2025-06-25"),
updatedAt: new Date("2025-06-25"),
isActive: true,
role: "admin"
},
{
id: 2,
username: "john",
email: "john@mail.com",
password: "hashedPassword2",
firstName: "John",
lastName: "Doe",
dateOfBirth: new Date("1990-08-24"),
address: "Hauptallee 23",
phoneNumber: "01234 2323 4232",
createdAt: new Date("2025-06-25"),
updatedAt: new Date("2025-06-25"),
isActive: true,
role: "user"
}
];
/**
* Generate new ID
* ---
* @returns {number}
*/
function generateNewId(): number {
return Math.max(...usersDatabase.map(u => u.id)) + 1;
}
/**
* Save user to database
* ---
* @param {I_User} user - User to save
* ---
* @returns {Promise<I_User>}
*/
async function saveUserToDatabase(user: I_User): Promise<I_User> {
const existingIndex = usersDatabase.findIndex(u => u.id === user.id);
if (existingIndex >= 0) {
// Update existing user
usersDatabase[existingIndex] = user;
} else {
// Add new user
usersDatabase.push(user);
}
return user;
}
/**
* Update user by ID
* ---
* @param {number} id - ID of the user
* @param {Partial<T_UserUpdateInput>} updates - Updated user data
* ---
* @returns {Promise<User>}
*/
async function updateUser(id: number, updates: Partial<T_UserUpdateInput>): Promise<I_User> {
const userToUpdate = usersDatabase.find(u => u.id === id);
// Prüfe, ob ein Benutzer gefunden wurde
if (!userToUpdate) {
throw new Error(`User with ID ${id} not found`);
}
// ID, createdAt, updatedAt können nicht
// versehentlich überschrieben werden.
const updatedUser = {
...userToUpdate,
...updates,
id, // ID bleibt unverändert
updatedAt: new Date() // Wird automatisch gesetzt
};
return saveUserToDatabase(updatedUser);
}
/**
* Create user
* ---
* @param {T_UserCreateInput} userData - New user data
* ---
* @returns {Promise<I_User>}
*/
async function createUser(userData: T_UserCreateInput): Promise<I_User> {
// Validierung - Benutzername
if (usersDatabase.some(u => u.username === userData.username)) {
throw new Error("Benutzername bereits vorhanden");
}
// Validierung - E-Mail
if (usersDatabase.some(u => u.email === userData.email)) {
throw new Error("E-Mail bereits vorhanden");
}
const newUser = {
...userData,
id: generateNewId(),
createdAt: new Date(),
updatedAt: new Date()
};
return saveUserToDatabase(newUser);
}
/**
* Get safe user
* ---
* @param {I_User} user - User to load
* ---
* @returns {T_UserSafe}
*/
function getUserSafe(user: I_User): T_UserSafe {
const { password, ...safeUser } = user;
return safeUser;
}
async function runExample() {
try {
console.log("Initiale Benutzer:", usersDatabase.map(u => getUserSafe(u)));
// 1. Benutzer erstellen
const newUser = await createUser({
username: "new_user",
email: "new@mail.com",
password: "hashedPassword1",
firstName: "New",
lastName: "User",
dateOfBirth: new Date("1995-05-05"),
address: "Hauptstraße 3",
phoneNumber: "1234 999 333",
isActive: true,
role: "user"
});
console.log("--- --- ---");
console.log("Benutzer erstellt:", getUserSafe(newUser));
// 2. Benutzer aktualisieren
const updatedUser = await updateUser(2, {
firstName: "Jonathan",
address: "Updated St",
phoneNumber: "555-0999-000"
});
console.log("--- --- ---");
console.log("Aktualisierter Benutzer:", getUserSafe(updatedUser));
// 3. Aktuelle Benutzer anzeigen
console.log("Aktuelle Benutzer");
usersDatabase.forEach(user => {
console.log(getUserSafe(user));
});
} catch (error) {
console.log("Fehler:", error instanceof Error ? error.message : error);
}
}
runExample();
Initiale Benutzer: [
{
id: 1,
username: 'admin',
email: 'admin@mail.com',
firstName: 'Admin',
lastName: 'User',
dateOfBirth: 1985-03-01T00:00:00.000Z,
address: 'Neue Straße 2',
phoneNumber: '0923 23123 45',
createdAt: 2025-06-25T00:00:00.000Z,
updatedAt: 2025-06-25T00:00:00.000Z,
isActive: true,
role: 'admin'
},
{
id: 2,
username: 'john',
email: 'john@mail.com',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: 1990-08-24T00:00:00.000Z,
address: 'Hauptallee 23',
phoneNumber: '01234 2323 4232',
createdAt: 2025-06-25T00:00:00.000Z,
updatedAt: 2025-06-25T00:00:00.000Z,
isActive: true,
role: 'user'
}
]
--- --- ---
Benutzer erstellt: {
username: 'new_user',
email: 'new@mail.com',
firstName: 'New',
lastName: 'User',
dateOfBirth: 1995-05-05T00:00:00.000Z,
address: 'Hauptstraße 3',
phoneNumber: '1234 999 333',
isActive: true,
role: 'user',
id: 3,
createdAt: 2025-06-25T12:27:16.999Z,
updatedAt: 2025-06-25T12:27:16.999Z
}
--- --- ---
Aktualisierter Benutzer: {
id: 2,
username: 'john',
email: 'john@mail.com',
firstName: 'Jonathan',
lastName: 'Doe',
dateOfBirth: 1990-08-24T00:00:00.000Z,
address: 'Updated St',
phoneNumber: '555-0999-000',
createdAt: 2025-06-25T00:00:00.000Z,
updatedAt: 2025-06-25T12:27:17.000Z,
isActive: true,
role: 'user'
}
Aktuelle Benutzer
{
id: 1,
username: 'admin',
email: 'admin@mail.com',
firstName: 'Admin',
lastName: 'User',
dateOfBirth: 1985-03-01T00:00:00.000Z,
address: 'Neue Straße 2',
phoneNumber: '0923 23123 45',
createdAt: 2025-06-25T00:00:00.000Z,
updatedAt: 2025-06-25T00:00:00.000Z,
isActive: true,
role: 'admin'
}
{
id: 2,
username: 'john',
email: 'john@mail.com',
firstName: 'Jonathan',
lastName: 'Doe',
dateOfBirth: 1990-08-24T00:00:00.000Z,
address: 'Updated St',
phoneNumber: '555-0999-000',
createdAt: 2025-06-25T00:00:00.000Z,
updatedAt: 2025-06-25T12:27:17.000Z,
isActive: true,
role: 'user'
}
{
username: 'new_user',
email: 'new@mail.com',
firstName: 'New',
lastName: 'User',
dateOfBirth: 1995-05-05T00:00:00.000Z,
address: 'Hauptstraße 3',
phoneNumber: '1234 999 333',
isActive: true,
role: 'user',
id: 3,
createdAt: 2025-06-25T12:27:16.999Z,
updatedAt: 2025-06-25T12:27:16.999Z
}
Record<K, T>
- Objekt-Typ mit bestimmten Schlüsseln
Record<K, T>
erstellt einen Objekttyp, dessen Eigenschaftsschlüsseln vom Typ K
sind und dessen Eigenschaftswerte vom Typ T
sind.
Record<K extends keyof any, T>
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Wie immer, wir fangen wir mit einem einfachen Beispiel an, um auf Anhieb zu sehen, was Record
tut.
type StringMap = Record<string, string>;
// Äquivalent zum oberen Typ
type StringMapEquivalent = {
[key: string]: string;
};
Die Objekte dieser Typen müssen Schlüsseln vom Typ String und Werte vom Typ String haben. Erzeugen wir ein paar Beispiel-Objekte mit den beiden (von ihrer Typisierung her identischen) Typen.
type StringObject = Record<string, string>;
type StringObjectEquivalent = {
[key: string]: string;
};
const objectOne = { "one": "100", "two": "200" };
const objectTwo = { "one": "100", "two": "200" };
console.log(objectOne);
console.log(objectTwo);
console.log("--- --- ---");
type NumberStringObject = Record<number, string>;
const objectThree = { 1: "One", 2: "Two", 3: "Three" };
console.log(objectThree);
{ one: '100', two: '200' }
{ one: '100', two: '200' }
--- --- ---
{ '1': 'One', '2': 'Two', '3': 'Three' }
Weitere Utility Types
Exclude<T, U>
- Union Typen filtern
Der Exclude<T, U>
Utility Type ist ein nützliches Werkzeug, um bestimmte Typen aus Union-Typen herauszufiltern.
type Exclude<T, U> = T extends U ? never : T;
Diese Definition nutzt Conditional Types und Distributive Conditional Types.
T extends U ?
- Prüft, ob der TypT
dem TypeU
zugeordnet werden kann.never
- Wenn ja, wird der Typ komplett entfernt (never
bedeutet “kein Typ”)T
- Wenn nein, bleibt der ursprüngliche Typ erhalten.
Beispiel
Werfen wir einen Blick auf das erste Beispiel, um etwas mehr Verständnis zu gewinnen.
type T_AllColors = "red" | "green" | "blue" | "yellow" | "orange";
type T_PrimaryColors = "red" | "green" | "blue";
type T_SecondaryColors = Exclude<T_AllColors, T_PrimaryColors>;
const colorOne: T_AllColors = "red";
const colorTwo: T_AllColors = "green";
const colorThree: T_AllColors = "blue";
const colorFour: T_AllColors = "yellow";
const colorFive: T_AllColors = "orange";
const primaryColors: T_PrimaryColors[] = [colorOne, colorTwo, colorThree];
// ❌ Funktioniert nicht
// const primaryColors: T_PrimaryColors[] = [colorOne, colorTwo, "orange"];
const secondaryColors: T_SecondaryColors[] = [colorFour, colorFive];
// ❌ Funktioniert nicht
// const secondaryColors: T_SecondaryColors[] = ["black", "white"];
console.log("Color one:", colorOne);
console.log("Color two: ", colorTwo);
console.log("Color three:", colorThree);
console.log("Color four:", colorFour);
console.log("Color five:", colorFive);
console.log("--- --- ---");
console.log("Primary colors:", primaryColors);
console.log("Secondary colors:", secondaryColors);
Color one: red
Color two: green
Color three: blue
Color four: yellow
Color five: orange
--- --- ---
Primary colors: [ 'red', 'green', 'blue' ]
Secondary colors: [ 'yellow', 'orange' ]
Extract<T, U>
- Gemeinsame Typen extrahieren
Der Extract<T, U>
Utility Type ist das Gegenstück zu Exclude<T, U>
. Er extrahiert nur die Typen aus T
, die auch in U
enthalten sind. Es ist wie eine Schnittmenge (Intersection) für Union-Typen.
type Extract<T, U> = T extends U ? T : never;
Diese Definition ist genau umgekehrt zu Exclude
.
T extends U ?
- Prüft, ob der TypT
dem TypU
zugeordnet werden kann.T
- Wenn ja, bleibt der TypT
erhalten.never
- Wenn nein, wird der Typ entfernt.
Es filtert also die Typen aus T
heraus, die nicht in U
vorkommen.
Ein einfaches Beispiel hierzu.
type T_ApiResponse =
| { status: "success", data: { id: number; name: string } }
| { status: "error", message: string }
| { status: "pending", timeout: number };
// Extrahiere nur Responses mit status="success"
type T_SuccessResponse = Extract<T_ApiResponse, { status: "success" }>;
/**
* Process response
* @param {T_SuccessResponse} response - Response
*/
function processResponse(response: T_SuccessResponse) {
console.log("Daten erhalten:", response.data.name);
}
// Simuliere Response
const successResponse: T_SuccessResponse = {
status: "success",
data: { id: 1, name: "TypeScript" }
};
const errorResponse: T_ApiResponse = {
status: "error",
message: "Fehler"
};
processResponse(successResponse);
Daten erhalten: TypeScript
NonNullable<T>
- Null und undefined ausschließen
Der Type NonNullable<T>
entfernt die Typen null
und undefined
aus einem gegebenen Typ T
. Das ist besonders nützlich, wenn man sicherstellen möchte, dass ein Wert niemals null
oder undefined
sein kann.
type NonNullable<T> = T extends null | undefined ? never : T;
- Für einen Typ
T
wird geprüft, ob diesernull
oderundefined
ist. - Falls ja, wird
never
zurückgegeben (was bedeutet, dass der Typ aus der Union entfernt wird). - Falls nein, wird der Typ
T
selbst beibehalten.
Beispiel
Betrachten wir ein einfaches Beispiel. In diesem Beispiel haben wir zwei verschiedene Typen. Ein regulärer Typ mit Eigenschaften die null
oder undefined
sein können und ein Typ, welcher alle Eigenschaften von diesem Basis-Typ übernimmt, außer die, die null
oder undefined
sein können.
type T_UserLight = {
id: number;
name: string | null;
email?: string | undefined;
};
type T_UserFull = {
[K in keyof T_UserLight]: NonNullable<T_UserLight[K]>;
};
// name oder email dürfen nicht gesetzt bleiben
const userLight: T_UserLight = {
id: 1,
name: null,
email: undefined
};
// name darf nicht null sein
// email ist optional, aber nicht undefined
const userFull: T_UserFull = {
id: 1,
name: "John",
email: "john@mail.com"
};
console.log(userLight);
console.log(userFull);
{ id: 1, name: null, email: undefined }
{ id: 1, name: 'John', email: 'john@mail.com' }
ReturnType<T>
- Rückgabetyp einer Funktion extrahieren
Der Utility Type ReturnType<T>
extrahiert den Rückgabetyp einer Funktion oder Methodensignatur. Gut zu gebrauchen, wenn man mit komplexen Funktionen arbeitet oder deren Rückgabetypen wiederverwenden möchte, ohne sie manuell zu kopieren.
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
- Generische Einschränkung:
T extends (...args: any) => any
: Stellt sicher, dassT
eine Funktion ist. - Type Inference mit
infer
: Deklariert einen PlatzhalterR
für den Rückgabetypen. WennT
eine Funktion ist, wirdR
zurückgegeben, sonstany
.
Beispiel 1
Ein einfaches Beispiel, bei dem der Rückgabetyp einer Funktion extrahiert wird und als Rückgabewert einer anderen Funktion verwendet wird. Einfacher gesagt: Wir haben eine Kern-Funktion, welche einen bestimmten Rückgabetyp hat. Es wird ein Typ definiert, welcher den Rückgabetyp der genannten Funktion hat.
/**
* Format currency
* ----
* @param {number} amount - Amount (price)
* @param {string} currency - Currency code
* ---
* @returns {string}
*/
function formatCurrency(amount: number, currency: string): string {
return new Intl.NumberFormat("de-DE", {
style: "currency",
currency
}).format(amount);
}
// Extrahiere den Rückgabetyp
type T_FormattedValue = ReturnType<typeof formatCurrency>; // string
/**
* Log result (wrapper function)
* ---
* @param {number} amount - Amount (price)
* @param {string} currency - Currency code
* ---
* @returns {T_FormattedValue}
*/
function logResult(amount: number, currency: string): T_FormattedValue {
const result = formatCurrency(amount, currency);
console.log("Formatted:", result);
return result;
}
console.log(logResult(42.99, "EUR"));
Formatted: 42,99 €
42,99 €
Rein technisch könnte man den Rückgabetyp der zweiten Funktion auch manuell platzieren. Hätter allerdings einen Effekt, dass man den Rückgabetyp überall ändern müsste.