TypeScript Mapped Types - Type Transformationen
Mapped Types transformieren existierende Types in neue Types.
Basic Mapped Types
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
type Optional<T> = {
[K in keyof T]?: T[K]
}
interface User {
name: string
age: number
}
type ReadonlyUser = Readonly<User>
// { readonly name: string; readonly age: number }
type OptionalUser = Optional<User>
// { name?: string; age?: number }
keyof Operator
interface Person {
name: string
age: number
email: string
}
type PersonKeys = keyof Person
// "name" | "age" | "email"
type PersonValues = Person[keyof Person]
// string | number
Mapped Type Syntax
type MappedType<T> = {
[K in keyof T]: T[K]
}
// Mit Modifiers
type ReadonlyNullable<T> = {
readonly [K in keyof T]?: T[K] | null
}
// Modifier entfernen
type Required<T> = {
[K in keyof T]-?: T[K] // -? entfernt optional
}
type Mutable<T> = {
-readonly [K in keyof T]: T[K] // -readonly entfernt readonly
}
Conditional Types
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// Mit infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function test(): string {
return "hello"
}
type T = ReturnType<typeof test> // string
Conditional Chains
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object"
type T1 = TypeName<string> // "string"
type T2 = TypeName<42> // "number"
type T3 = TypeName<() => void> // "function"
Template Literal Types
type Greeting = `Hello ${string}`
const a: Greeting = "Hello World" // ✅ OK
const b: Greeting = "Hi World" // ❌ Error
// Mit Union Types
type Direction = "top" | "right" | "bottom" | "left"
type Margin = `margin-${Direction}`
// "margin-top" | "margin-right" | "margin-bottom" | "margin-left"
// Kombinationen
type Color = "red" | "blue"
type Size = "sm" | "lg"
type ClassName = `${Color}-${Size}`
// "red-sm" | "red-lg" | "blue-sm" | "blue-lg"
String Manipulation Types
// Built-in
type Uppercase<S extends string> // "HELLO"
type Lowercase<S extends string> // "hello"
type Capitalize<S extends string> // "Hello"
type Uncapitalize<S extends string> // "hELLO"
type Loud = Uppercase<"hello"> // "HELLO"
type Quiet = Lowercase<"WORLD"> // "world"
// Custom
type GetterName<T extends string> = `get${Capitalize<T>}`
type SetterName<T extends string> = `set${Capitalize<T>}`
type UserGetter = GetterName<"name"> // "getName"
type UserSetter = SetterName<"age"> // "setAge"
Advanced Mapped Types
Deep Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K]
}
interface User {
name: string
address: {
street: string
city: string
}
}
type ReadonlyUser = DeepReadonly<User>
// {
// readonly name: string
// readonly address: {
// readonly street: string
// readonly city: string
// }
// }
Getters Type
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface User {
name: string
age: number
}
type UserGetters = Getters<User>
// {
// getName: () => string
// getAge: () => number
// }
Filters
type FilterByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
interface User {
name: string
age: number
email: string
isActive: boolean
}
type StringProps = FilterByType<User, string>
// { name: string; email: string }
type NumberProps = FilterByType<User, number>
// { age: number }
// Event Definitions
interface Events {
"user:login": { userId: string; timestamp: Date }
"user:logout": { userId: string }
"post:create": { postId: string; authorId: string }
"post:delete": { postId: string }
}
// Event Handler Type
type EventHandler<T> = (data: T) => void | Promise<void>
// Mapped Type für Event Handlers
type EventHandlers = {
[K in keyof Events]: EventHandler<Events[K]>[]
}
// Event Emitter Class
class EventEmitter {
private handlers: Partial<EventHandlers> = {}
on<K extends keyof Events>(
event: K,
handler: EventHandler<Events[K]>
): void {
if (!this.handlers[event]) {
this.handlers[event] = []
}
this.handlers[event]!.push(handler)
}
emit<K extends keyof Events>(
event: K,
data: Events[K]
): void {
const eventHandlers = this.handlers[event]
if (!eventHandlers) return
eventHandlers.forEach(handler => handler(data))
}
off<K extends keyof Events>(
event: K,
handler: EventHandler<Events[K]>
): void {
const eventHandlers = this.handlers[event]
if (!eventHandlers) return
const index = eventHandlers.indexOf(handler)
if (index > -1) {
eventHandlers.splice(index, 1)
}
}
}
// Usage - Vollständig Type-Safe!
const emitter = new EventEmitter()
emitter.on("user:login", (data) => {
// data ist Type: { userId: string; timestamp: Date }
console.log(`User ${data.userId} logged in`)
})
emitter.emit("user:login", {
userId: "123",
timestamp: new Date()
})
emitter.emit("post:create", {
postId: "post-1",
authorId: "user-1"
})Distributive Conditional Types
type ToArray<T> = T extends any ? T[] : never
type A = ToArray<string | number>
// string[] | number[] (distributed!)
// Non-Distributive mit []
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never
type B = ToArrayNonDist<string | number>
// (string | number)[] (nicht distributed)
Recursive Mapped Types
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? DeepPartial<T[K]>
: T[K]
}
interface Config {
database: {
host: string
port: number
credentials: {
username: string
password: string
}
}
}
type PartialConfig = DeepPartial<Config>
// Alles optional, auch nested!
📝 Quiz
Was macht der 'as' Clause in Mapped Types?
Tipps & Tricks
Type-Safe Object.keys
function keys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[]
}
const user = { name: "Max", age: 25 }
const userKeys = keys(user) // ("name" | "age")[]
Exclude Specific Keys
type OmitMultiple<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P]
}
interface User {
id: number
name: string
email: string
password: string
}
type PublicUser = OmitMultiple<User, "password" | "email">
// { id: number; name: string }
Type-Safe Pick
function pick<T, K extends keyof T>(
obj: T,
...keys: K[]
): Pick<T, K> {
const result = {} as Pick<T, K>
keys.forEach(key => {
result[key] = obj[key]
})
return result
}
const user = { name: "Max", age: 25, email: "max@test.com" }
const picked = pick(user, "name", "age")
// { name: string; age: number }
Häufige Fehler
Fehler 1: Conditional Type ohne extends
❌ FALSCH:
type Test<T> = T === string ? true : false // ❌ Error
✅ RICHTIG:
type Test<T> = T extends string ? true : false
Fehler 2: Mapped Type ohne 'in'
❌ FALSCH:
type Test<T> = {
[K of keyof T]: T[K] // ❌ Error
}
✅ RICHTIG:
type Test<T> = {
[K in keyof T]: T[K]
}
Fehler 3: Recursive Type ohne Base Case
❌ UNENDLICH:
type Flatten<T> = T extends any[]
? Flatten<T[number]> // Kein Base Case!
: T
✅ RICHTIG:
type Flatten<T> = T extends (infer U)[]
? U extends any[]
? Flatten<U>
: U
: T
Zusammenfassung
Du hast gelernt:
- ✅ Mapped Types Grundlagen
- ✅ keyof und in Operator
- ✅ Conditional Types mit extends
- ✅ Template Literal Types
- ✅ Advanced Patterns (Deep Types)
- ✅ Distributive Conditional Types
Key Takeaways:
- Mapped Types transformieren Types
- [K in keyof T] für Iteration
- Conditional mit extends
- Template Literals für String-Types
- Rekursion für Deep Types
- as Clause für Key Remapping
Nächste Schritte
Mapped Types sind essenziell für:
- Utility Types Library
- Type-Safe APIs
- ORM Frameworks
- Form Libraries
Viel Erfolg! 🚀
Gut gemacht! 🎉
Du hast "TypeScript Mapped Types - Type Transformationen" abgeschlossen
Artikel bewerten
Bitte einloggen um zu bewerten
Das könnte dich auch interessieren
TypeScript Decorators - Metaprogrammierung
Lerne Decorators für Classes, Methods und Properties. Experimentelle Features für fortgeschrittene Patterns.
TypeScript Generics - Wiederverwendbare Typen
Lerne Generics in TypeScript - das Werkzeug für flexible und wiederverwendbare Komponenten, Funktionen und Classes.
TypeScript Utility Types - Eingebaute Type Helpers
Lerne die wichtigsten eingebauten Utility Types: Partial, Required, Pick, Omit, Record und mehr.