TypeScript Generics - Fortgeschrittene Typen in der Praxis
TypeScript Generics Fortgeschrittene
technologieTypeScript Generics - Fortgeschrittene Typen in der Praxis
Generics sind einer der maechtigsten Mechanismen in TypeScript und ermoeglichen die Erstellung wiederverwendbarer, typsicherer Komponenten. In diesem Artikel tauchen wir in fortgeschrittene Techniken fuer die Arbeit mit Generics ein - von einfachen generischen Funktionen bis hin zu komplexen Entwurfsmustern, die in Produktionsanwendungen eingesetzt werden.
Generische Funktionen#
Generische Funktionen ermoeglichen es, Code zu schreiben, der mit verschiedenen Typen funktioniert und dabei volle Typsicherheit bewaehrt. Anstatt any zu verwenden, definieren wir einen Typparameter, den TypeScript basierend auf den uebergebenen Argumenten inferiert.
// Einfache generische Funktion
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // typ: string
const num = identity(42); // typ: number
// Funktion mit mehreren Typparametern
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair("name", 42); // typ: [string, number]
// Generische Funktion mit Callback
function map<T, U>(array: T[], fn: (item: T, index: number) => U): U[] {
return array.map(fn);
}
const lengths = map(["hello", "world"], (s) => s.length); // typ: number[]
Generische Funktionen mit Standardtypen erleichtern die Nutzung von APIs, wenn kein spezifischer Typ benoetigt wird:
function createState<T = string>(initial: T): {
get: () => T;
set: (value: T) => void;
} {
let state = initial;
return {
get: () => state,
set: (value: T) => { state = value; },
};
}
const stringState = createState("hello"); // T = string (inferred)
const numberState = createState<number>(0); // T = number (explicit)
Generische Klassen#
Generische Klassen ermoeglichen die Erstellung von Datenstrukturen und Diensten, die mit jedem Typ funktionieren und dabei volle Kontrolle ueber die Typen bieten.
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.peek(); // typ: number | undefined
// Generische Klasse mit mehreren Parametern
class KeyValueStore<K extends string | number, V> {
private store = new Map<K, V>();
set(key: K, value: V): void {
this.store.set(key, value);
}
get(key: K): V | undefined {
return this.store.get(key);
}
entries(): [K, V][] {
return Array.from(this.store.entries());
}
}
const userStore = new KeyValueStore<string, { name: string; age: number }>();
userStore.set("user1", { name: "Jan", age: 30 });
Generische Interfaces#
Generische Interfaces definieren Vertraege, die mit Typen parametrisiert werden koennen. Sie sind das Fundament vieler Entwurfsmuster in TypeScript.
// Generisches API-Antwort-Interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
// Generisches Interface mit Paginierung
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
hasNext: boolean;
hasPrevious: boolean;
}
// Verwendung
interface User {
id: string;
name: string;
email: string;
}
type UserResponse = ApiResponse<User>;
type UserListResponse = ApiResponse<PaginatedResponse<User>>;
// Generisches Interface mit Methoden
interface Comparable<T> {
compareTo(other: T): number;
equals(other: T): boolean;
}
class Money implements Comparable<Money> {
constructor(
public amount: number,
public currency: string
) {}
compareTo(other: Money): number {
if (this.currency !== other.currency) {
throw new Error("Cannot compare different currencies");
}
return this.amount - other.amount;
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}
Typeinschraenkungen (extends)#
Das Schluesselwort extends im Kontext von Generics ermoeglicht es, Einschraenkungen fuer Typparameter festzulegen und sicherzustellen, dass der uebergebene Typ bestimmte Anforderungen erfuellt.
// Einschraenkung auf Objekte mit einer length-Eigenschaft
function logLength<T extends { length: number }>(item: T): T {
console.log(`Length: ${item.length}`);
return item;
}
logLength("hello"); // OK - string ma length
logLength([1, 2, 3]); // OK - array ma length
// logLength(42); // Błąd - number nie ma length
// Einschraenkung mit keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Jan", age: 30, email: "jan@example.com" };
const name = getProperty(user, "name"); // typ: string
const age = getProperty(user, "age"); // typ: number
// getProperty(user, "phone"); // Błąd - "phone" nie istnieje w User
// Einschraenkung mit mehreren Constraints
interface HasId {
id: string;
}
interface HasTimestamps {
createdAt: Date;
updatedAt: Date;
}
function updateEntity<T extends HasId & HasTimestamps>(
entity: T,
updates: Partial<Omit<T, "id" | "createdAt">>
): T {
return {
...entity,
...updates,
updatedAt: new Date(),
};
}
Conditional Types (Bedingte Typen)#
Conditional Types ermoeglichen die Erstellung von Typen, die von Bedingungen abhaengen. Sie funktionieren aehnlich wie der ternaere Operator, jedoch auf der Ebene des Typsystems.
// Einfacher Conditional Type
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Conditional Type mit infer
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type C = UnwrapPromise<Promise<string>>; // string
type D = UnwrapPromise<number>; // number
// Verschachtelte Conditional Types
type DeepUnwrap<T> = T extends Promise<infer U>
? DeepUnwrap<U>
: T;
type E = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string
// Distributive conditional types
type NonNullable<T> = T extends null | undefined ? never : T;
type F = NonNullable<string | null | undefined>; // string
// Praktisches Beispiel - Typen aus Funktionen extrahieren
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type G = FunctionReturnType<() => string>; // string
type H = FunctionReturnType<(x: number) => boolean>; // boolean
Mapped Types#
Mapped Types ermoeglichen die Transformation bestehender Typen durch Iteration ueber deren Schluessel. Dies ist ein maaechtiges Werkzeug zur Erstellung von Typvarianten.
// Einfacher Mapped Type - alle Felder optional
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Alle Felder readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Erweiterter Mapped Type - nullable Felder
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Mapped Type mit - Modifikator
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Mapped Type mit Schluessel-Remapping (as)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
type PersonSetters = Setters<Person>;
// { setName: (value: string) => void; setAge: (value: number) => void; }
// Filtern von Schluesseln in Mapped Types
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
active: boolean;
}
type StringFields = OnlyStrings<Mixed>;
// { name: string; email: string; }
Template Literal Types#
Template Literal Types ermoeglichen die Erstellung von Typen basierend auf String-Templates, was besonders nuetzlich bei der Definition von APIs und Event-Handlern ist.
// Einfache Template Literal Types
type EventName = `on${Capitalize<"click" | "focus" | "blur">}`;
// "onClick" | "onFocus" | "onBlur"
// Dynamische Event-Namensgenerierung
type DOMEvents = "click" | "mouseover" | "mouseout" | "keydown" | "keyup";
type EventHandler = `on${Capitalize<DOMEvents>}`;
// Template Literal mit Generics
type PropEventType<T extends string> = `${T}Changed`;
type UserEvents = PropEventType<"name" | "email" | "age">;
// "nameChanged" | "emailChanged" | "ageChanged"
// Fortgeschritten - Parsen von Template Literals
type ExtractRouteParams<T extends string> =
T extends `${infer _}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<`/${Rest}`>
: T extends `${infer _}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/users/:userId/posts/:postId">;
// "userId" | "postId"
// CSS Unit types
type CSSUnit = "px" | "em" | "rem" | "vh" | "vw" | "%";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px"; // OK
const height: CSSValue = "50vh"; // OK
// const bad: CSSValue = "abc"; // Błąd
Das Schluesselwort infer#
Das Schluesselwort infer ermoeglicht es, Typen aus dem Inneren anderer Typen zu extrahieren. Es ist ausschliesslich innerhalb von Conditional Types verfuegbar und ist eines der maechtigsten Werkzeuge im TypeScript-Typsystem.
// Elementtyp aus Array extrahieren
type ElementType<T> = T extends (infer U)[] ? U : never;
type I = ElementType<string[]>; // string
type J = ElementType<number[]>; // number
// Funktionsargumenttypen extrahieren
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type K = FirstArg<(name: string, age: number) => void>; // string
// Typ aus Promise extrahieren
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// Props aus React-Komponente extrahieren
type PropsOf<T> = T extends React.ComponentType<infer P> ? P : never;
// Rueckgabetyp aus async-Funktion extrahieren
type AsyncReturnType<T extends (...args: any[]) => Promise<any>> =
T extends (...args: any[]) => Promise<infer R> ? R : never;
type UserData = AsyncReturnType<() => Promise<{ id: string; name: string }>>;
// { id: string; name: string }
// Infer mit Template Literal Types
type ParseQueryString<T extends string> =
T extends `${infer Key}=${infer Value}&${infer Rest}`
? { [K in Key]: Value } & ParseQueryString<Rest>
: T extends `${infer Key}=${infer Value}`
? { [K in Key]: Value }
: {};
Utility Types - Eingehende Analyse#
TypeScript bietet einen umfangreichen Satz eingebauter Utility Types. Das Verstaendnis ihrer Implementierung ermoeglicht die Erstellung eigener fortgeschrittener Typen.
// Partial<T> - alle Felder optional
type MyPartial<T> = { [K in keyof T]?: T[K] };
// Required<T> - alle Felder erforderlich
type MyRequired<T> = { [K in keyof T]-?: T[K] };
// Pick<T, K> - Teilmenge der Schluessel auswaehlen
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
// Omit<T, K> - ausgewaehlte Schluessel auslassen
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// Record<K, V> - Typ mit Schluesseln K und Werten V
type MyRecord<K extends keyof any, V> = { [P in K]: V };
// Extract<T, U> - Typen aus Union extrahieren, die U entsprechen
type MyExtract<T, U> = T extends U ? T : never;
// Exclude<T, U> - Typen aus Union ausschliessen, die U entsprechen
type MyExclude<T, U> = T extends U ? never : T;
// ReturnType<T> - Funktionsrueckgabetyp
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : any;
// Parameters<T> - Funktionsparametertypen als Tuple
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
Praktische Anwendungen von Utility Types:
interface Product {
id: string;
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
createdAt: Date;
}
// Typ zum Erstellen eines neuen Produkts (ohne auto-generierte Felder)
type CreateProduct = Omit<Product, "id" | "createdAt">;
// Typ zum Aktualisieren eines Produkts (alles optional)
type UpdateProduct = Partial<Omit<Product, "id">>;
// Typ fuer Listenanzeige
type ProductListItem = Pick<Product, "id" | "name" | "price" | "inStock">;
// Zuordnung von Kategorien zu Produkten
type ProductsByCategory = Record<string, Product[]>;
// Nur String-Schluessel extrahieren
type StringKeys = Extract<keyof Product, string>;
// Event-Typen
type ProductEvent = "created" | "updated" | "deleted" | "archived";
type ActiveEvent = Exclude<ProductEvent, "archived">;
// "created" | "updated" | "deleted"
Branded Types#
Branded Types ermoeglichen die Erstellung nominaler Typen im strukturellen Typsystem von TypeScript. Sie verhindern das versehentliche Uebergeben von Werten mit demselben Basistyp, aber unterschiedlicher semantischer Bedeutung.
// Branded-Type-Definition
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
type Email = Brand<string, "Email">;
type USD = Brand<number, "USD">;
type EUR = Brand<number, "EUR">;
// Smart Constructors
function createUserId(id: string): UserId {
if (!id.match(/^usr_[a-zA-Z0-9]+$/)) {
throw new Error("Invalid user ID format");
}
return id as UserId;
}
function createEmail(email: string): Email {
if (!email.includes("@")) {
throw new Error("Invalid email format");
}
return email as Email;
}
function createUSD(amount: number): USD {
return Math.round(amount * 100) / 100 as USD;
}
// Jetzt schuetzt TypeScript vor Fehlern
function getUser(id: UserId): Promise<User> {
return fetch(`/api/users/${id}`).then(r => r.json());
}
function getOrder(id: OrderId): Promise<Order> {
return fetch(`/api/orders/${id}`).then(r => r.json());
}
const userId = createUserId("usr_abc123");
const orderId = "ord_xyz789" as OrderId;
getUser(userId); // OK
// getUser(orderId); // Błąd! OrderId nie jest UserId
// getUser("some-string"); // Błąd! string nie jest UserId
// Branded Types verhindern das Mischen von Waehrungen
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD;
}
const price = createUSD(29.99);
const tax = createUSD(5.00);
addUSD(price, tax); // OK
// addUSD(price, 100 as EUR); // Błąd!
Discriminated Unions#
Discriminated Unions sind ein Muster, bei dem ein gemeinsames Feld (Diskriminator) TypeScript ermoeglicht, den Union-Typ einzugrenzen.
// Definition einer Discriminated Union
interface LoadingState {
status: "loading";
}
interface SuccessState<T> {
status: "success";
data: T;
}
interface ErrorState {
status: "error";
error: {
message: string;
code: number;
};
}
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
// Exhaustive pattern matching
function renderState<T>(state: AsyncState<T>): string {
switch (state.status) {
case "loading":
return "Loading...";
case "success":
return `Data: ${JSON.stringify(state.data)}`;
case "error":
return `Error ${state.error.code}: ${state.error.message}`;
default:
// Vollstaendigkeitspruefung - sollte nie erreicht werden
const _exhaustive: never = state;
return _exhaustive;
}
}
// Komplexeres Beispiel - Zahlungssystem
type PaymentMethod =
| { type: "card"; cardNumber: string; expiryDate: string; cvv: string }
| { type: "bankTransfer"; iban: string; bic: string }
| { type: "paypal"; email: string }
| { type: "crypto"; walletAddress: string; network: "ethereum" | "bitcoin" };
function processPayment(method: PaymentMethod): void {
switch (method.type) {
case "card":
console.log(`Processing card ending in ${method.cardNumber.slice(-4)}`);
break;
case "bankTransfer":
console.log(`Transferring to IBAN: ${method.iban}`);
break;
case "paypal":
console.log(`PayPal payment to ${method.email}`);
break;
case "crypto":
console.log(`Crypto payment on ${method.network}: ${method.walletAddress}`);
break;
}
}
// Generischer Result-Typ (inspiriert von Rust)
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return err("Division by zero");
return ok(a / b);
}
const result = divide(10, 2);
if (result.ok) {
console.log(result.value); // typ: number
} else {
console.log(result.error); // typ: string
}
Pattern Matching mit Generics#
TypeScript hat kein eingebautes Pattern Matching wie Rust oder Scala, aber wir koennen es mit Generics und Conditional Types simulieren.
// Pattern Matching auf Typebene
type Match<T, Cases extends [any, any][]> =
Cases extends [[infer Pattern, infer Result], ...infer Rest extends [any, any][]]
? T extends Pattern
? Result
: Match<T, Rest>
: never;
// Praktischer Wertebene-Matcher
type Matcher<T, R> = {
[K in T extends { type: infer U extends string } ? U : never]: (
value: Extract<T, { type: K }>
) => R;
};
function match<T extends { type: string }, R>(
value: T,
handlers: Matcher<T, R>
): R {
const handler = (handlers as any)[value.type];
return handler(value);
}
// Verwendung z discriminated union
type Shape =
| { type: "circle"; radius: number }
| { type: "rectangle"; width: number; height: number }
| { type: "triangle"; base: number; height: number };
const area = match<Shape, number>(
{ type: "circle", radius: 5 },
{
circle: ({ radius }) => Math.PI * radius ** 2,
rectangle: ({ width, height }) => width * height,
triangle: ({ base, height }) => (base * height) / 2,
}
);
Praktische Entwurfsmuster#
Repository-Muster#
Das generische Repository-Muster abstrahiert Datenoperationen und ermoeglicht das Schreiben von Code unabhaengig von der Datenquelle.
interface Entity {
id: string;
createdAt: Date;
updatedAt: Date;
}
interface Repository<T extends Entity> {
findById(id: string): Promise<T | null>;
findAll(filter?: Partial<T>): Promise<T[]>;
create(data: Omit<T, "id" | "createdAt" | "updatedAt">): Promise<T>;
update(id: string, data: Partial<Omit<T, "id" | "createdAt">>): Promise<T>;
delete(id: string): Promise<boolean>;
count(filter?: Partial<T>): Promise<number>;
}
interface User extends Entity {
name: string;
email: string;
role: "admin" | "user";
}
interface Product extends Entity {
name: string;
price: number;
category: string;
}
class InMemoryRepository<T extends Entity> implements Repository<T> {
private items: Map<string, T> = new Map();
async findById(id: string): Promise<T | null> {
return this.items.get(id) ?? null;
}
async findAll(filter?: Partial<T>): Promise<T[]> {
let results = Array.from(this.items.values());
if (filter) {
results = results.filter((item) =>
Object.entries(filter).every(
([key, value]) => item[key as keyof T] === value
)
);
}
return results;
}
async create(data: Omit<T, "id" | "createdAt" | "updatedAt">): Promise<T> {
const now = new Date();
const entity = {
...data,
id: crypto.randomUUID(),
createdAt: now,
updatedAt: now,
} as T;
this.items.set(entity.id, entity);
return entity;
}
async update(
id: string,
data: Partial<Omit<T, "id" | "createdAt">>
): Promise<T> {
const existing = this.items.get(id);
if (!existing) throw new Error(`Entity ${id} not found`);
const updated = { ...existing, ...data, updatedAt: new Date() };
this.items.set(id, updated);
return updated;
}
async delete(id: string): Promise<boolean> {
return this.items.delete(id);
}
async count(filter?: Partial<T>): Promise<number> {
const items = await this.findAll(filter);
return items.length;
}
}
// Verwendung
const userRepo: Repository<User> = new InMemoryRepository<User>();
const productRepo: Repository<Product> = new InMemoryRepository<Product>();
Builder-Muster#
Der generische Builder ermoeglicht den schrittweisen Aufbau von Objekten mit voller Typunterstuetzung.
class Builder<T extends Record<string, any>> {
private data: Partial<T> = {};
set<K extends keyof T>(key: K, value: T[K]): this {
this.data[key] = value;
return this;
}
build(): T {
return this.data as T;
}
}
// Typsicherer Builder mit erforderlichen Feldern
type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> &
Partial<Omit<T, K>>;
class TypeSafeBuilder<T extends Record<string, any>, Set extends keyof T = never> {
private data: Partial<T> = {};
set<K extends keyof T>(
key: K,
value: T[K]
): TypeSafeBuilder<T, Set | K> {
this.data[key] = value;
return this as any;
}
build(this: TypeSafeBuilder<T, keyof T>): T {
return this.data as T;
}
}
interface Config {
host: string;
port: number;
database: string;
ssl: boolean;
}
const config = new TypeSafeBuilder<Config>()
.set("host", "localhost")
.set("port", 5432)
.set("database", "mydb")
.set("ssl", true)
.build(); // OK - wszystkie pola ustawione
Factory-Muster#
Eine generische Factory ermoeglicht die Erstellung von Instanzen verschiedener Typen mit einem gemeinsamen Interface.
// Generisches Factory-Interface
interface Factory<T> {
create(...args: any[]): T;
}
// Registry-Muster mit Generics
class ServiceRegistry {
private factories = new Map<string, Factory<any>>();
register<T>(name: string, factory: Factory<T>): void {
this.factories.set(name, factory);
}
resolve<T>(name: string): T {
const factory = this.factories.get(name);
if (!factory) {
throw new Error(`Service "${name}" not registered`);
}
return factory.create();
}
}
// Typsicherer Event-Emitter mit Generics
type EventMap = Record<string, any>;
class TypedEventEmitter<Events extends EventMap> {
private handlers = new Map<keyof Events, Set<Function>>();
on<K extends keyof Events>(
event: K,
handler: (payload: Events[K]) => void
): () => void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
// Unsubscribe-Funktion zurueckgeben
return () => {
this.handlers.get(event)?.delete(handler);
};
}
emit<K extends keyof Events>(event: K, payload: Events[K]): void {
this.handlers.get(event)?.forEach((handler) => handler(payload));
}
}
// Verwendung
interface AppEvents {
userLoggedIn: { userId: string; timestamp: Date };
orderPlaced: { orderId: string; total: number };
error: { message: string; code: number };
}
const emitter = new TypedEventEmitter<AppEvents>();
emitter.on("userLoggedIn", ({ userId, timestamp }) => {
console.log(`User ${userId} logged in at ${timestamp}`);
});
emitter.on("orderPlaced", ({ orderId, total }) => {
console.log(`Order ${orderId} placed: $${total}`);
});
emitter.emit("userLoggedIn", {
userId: "usr_123",
timestamp: new Date(),
});
// emitter.emit("userLoggedIn", { wrong: "data" }); // Błąd!
Fortgeschrittene Typkompositionstechniken#
Die Kombination mehrerer fortgeschrittener Techniken ermoeglicht die Erstellung aeusserst ausdrucksstarker und sicherer APIs.
// Deep Partial - rekursiv optionale Felder
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// Deep Readonly - rekursiv unveraenderlich
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// Paths - Pfade zu verschachtelten Objektfeldern generieren
type Paths<T, Prefix extends string = ""> = {
[K in keyof T & string]: T[K] extends object
? Paths<T[K], `${Prefix}${K}.`> | `${Prefix}${K}`
: `${Prefix}${K}`;
}[keyof T & string];
interface NestedConfig {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
cache: {
ttl: number;
enabled: boolean;
};
}
type ConfigPaths = Paths<NestedConfig>;
// "database" | "database.host" | "database.port" | "database.credentials" | ...
// Typsicherer tiefer Zugriff
function get<T, P extends string>(
obj: T,
path: P
): P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends Paths<T[K]>
? any
: never
: never
: P extends keyof T
? T[P]
: never {
return path.split(".").reduce((acc: any, key) => acc?.[key], obj);
}
Zusammenfassung#
Generics in TypeScript sind das Fundament fuer die Erstellung skalierbaren, sicheren und wiederverwendbaren Codes. Die wichtigsten Techniken, die wir behandelt haben, umfassen:
- Generische Funktionen, Klassen und Interfaces - das Fundament der Code-Wiederverwendbarkeit
- Einschraenkungen (extends) - praezise Kontrolle ueber erlaubte Typen
- Conditional Types - Logik auf der Ebene des Typsystems
- Mapped Types - Transformation bestehender Typen
- Template Literal Types - Operationen auf String-Typen
- Infer - Extraktion von Typen aus dem Inneren anderer Typen
- Utility Types - fertige Werkzeuge zur Typmanipulation
- Branded Types - nominale Typisierung fuer groessere Sicherheit
- Discriminated Unions - sichere Zustandsmodellierung
- Entwurfsmuster - Repository, Builder, Factory mit Generics
Die Beherrschung dieser Techniken ermoeglicht die Erstellung von Typsystemen, die die Geschaeftslogik widerspiegeln und Fehler zur Kompilierzeit abfangen, bevor sie in die Produktion gelangen.
Bei MDS Software Solutions Group sind TypeScript und fortgeschrittene Generics Teil der taeglichen Arbeit unseres Teams. Wir erstellen typsichere Anwendungen, die einfacher zu warten und weiterzuentwickeln sind. Brauchen Sie Unterstuetzung bei einem TypeScript-Projekt? Kontaktieren Sie uns - wir helfen gerne!
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.