TypeScript Type Guards - Type Narrowing
Type Guards erlauben sichere Type-Checks zur Laufzeit mit Type Narrowing.
typeof Type Guards
Für primitive Types:
function process(value: string | number) {
if (typeof value === "string") {
// value ist hier Type: string
return value.toUpperCase()
}
// value ist hier Type: number
return value.toFixed(2)
}
Supported typeof Values
typeof value === "string"
typeof value === "number"
typeof value === "boolean"
typeof value === "symbol"
typeof value === "undefined"
typeof value === "object" // ⚠️ auch null!
typeof value === "function"
typeof Fallstrick
function isObject(value: unknown): boolean {
return typeof value === "object" // ⚠️ true für null!
}
// Besser:
function isObject(value: unknown): boolean {
return typeof value === "object" && value !== null
}
instanceof Type Guards
Für Class Instances:
class Dog {
bark() {
console.log("Woof!")
}
}
class Cat {
meow() {
console.log("Meow!")
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark() // Type: Dog
} else {
animal.meow() // Type: Cat
}
}
Mit Error Handling
function handleError(error: unknown) {
if (error instanceof Error) {
console.error(error.message)
console.error(error.stack)
} else {
console.error("Unknown error:", error)
}
}
in Operator
Property-Check:
interface Car {
drive(): void
}
interface Boat {
sail(): void
}
function move(vehicle: Car | Boat) {
if ("drive" in vehicle) {
vehicle.drive() // Type: Car
} else {
vehicle.sail() // Type: Boat
}
}
Nested Property Check
interface User {
name: string
address?: {
street: string
city: string
}
}
function printAddress(user: User) {
if ("address" in user && user.address) {
console.log(`${user.address.street}, ${user.address.city}`)
}
}
Custom Type Guards
Mit is Predicate:
interface Fish {
swim(): void
}
interface Bird {
fly(): void
}
// Type Predicate
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined
}
function move(animal: Fish | Bird) {
if (isFish(animal)) {
animal.swim() // Type: Fish
} else {
animal.fly() // Type: Bird
}
}
Null/Undefined Check
function isNotNull<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined
}
const values = [1, null, 2, undefined, 3]
const numbers = values.filter(isNotNull) // Type: number[]
Array Type Guard
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string")
}
function process(value: unknown) {
if (isStringArray(value)) {
value.forEach(str => console.log(str.toUpperCase()))
}
}
Discriminated Unions
Type Guard mit gemeinsamer Property:
interface Success {
success: true
data: string
}
interface Failure {
success: false
error: string
}
type Result = Success | Failure
function handleResult(result: Result) {
if (result.success) {
console.log(result.data) // Type: Success
} else {
console.error(result.error) // Type: Failure
}
}
Mit Kind Property
interface Circle {
kind: "circle"
radius: number
}
interface Rectangle {
kind: "rectangle"
width: number
height: number
}
type Shape = Circle | Rectangle
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2
}
return shape.width * shape.height
}
Equality Narrowing
Vergleichs-basiertes Narrowing:
function process(x: string | number, y: string | boolean) {
if (x === y) {
// x und y sind beide string
console.log(x.toUpperCase())
console.log(y.toUpperCase())
}
}
Truthiness Narrowing
function printName(name: string | null | undefined) {
if (name) {
// name ist string (nicht null/undefined)
console.log(name.toUpperCase())
}
}
// Vorsicht mit 0, "", false!
function printValue(value: string | number | null) {
if (value) {
console.log(value) // 0 würde übersprungen!
}
}
// Besser:
function printValue(value: string | number | null) {
if (value !== null && value !== undefined) {
console.log(value) // 0 wird geprinted
}
}
Control Flow Analysis
TypeScript analysiert Code Flow:
function process(value: string | null) {
if (value === null) {
return
}
// value ist hier automatisch string (null wurde ausgeschlossen)
console.log(value.toUpperCase())
}
Mit Multiple Returns
function getLength(value: string | null | undefined): number {
if (!value) {
return 0
}
// value ist hier string
return value.length
}
Mit throw
function assertString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string")
}
}
function process(value: unknown) {
assertString(value)
// value ist hier string
console.log(value.toUpperCase())
}
Assertion Functions
Mit asserts Keyword:
function assertIsDefined<T>(value: T | null | undefined): asserts value is T {
if (value === null || value === undefined) {
throw new Error("Value is null or undefined")
}
}
const user: { name: string } | null = getUser()
assertIsDefined(user)
// user ist hier { name: string } (nicht null)
console.log(user.name)
Mit Custom Message
function assert(condition: boolean, message: string): asserts condition {
if (!condition) {
throw new Error(message)
}
}
function divide(a: number, b: number): number {
assert(b !== 0, "Division by zero")
return a / b
}
interface ApiSuccess<T> {
success: true
data: T
timestamp: Date
}
interface ApiError {
success: false
error: string
code: string
timestamp: Date
}
type ApiResponse<T> = ApiSuccess<T> | ApiError
// Type Guard
function isSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
return response.success === true
}
// Handler
async function fetchUser(id: number): Promise<User> {
const response = await api.get<User>(`/users/${id}`)
if (isSuccess(response)) {
// response ist ApiSuccess<User>
console.log("User loaded:", response.data.name)
return response.data
}
// response ist ApiError
console.error("Error:", response.error)
throw new Error(`Failed to load user: ${response.code}`)
}
// Mit Assertion Function
function assertSuccess<T>(response: ApiResponse<T>): asserts response is ApiSuccess<T> {
if (!response.success) {
throw new Error(`API Error: ${response.error}`)
}
}
async function loadUser(id: number): Promise<User> {
const response = await api.get<User>(`/users/${id}`)
assertSuccess(response)
// response ist jetzt ApiSuccess<User>
return response.data
}📝 Quiz
Was macht ein Type Guard mit 'is' Predicate?
Tipps & Tricks
Array.isArray Type Guard
function process(value: string | string[]) {
if (Array.isArray(value)) {
value.forEach(s => console.log(s)) // Type: string[]
} else {
console.log(value.toUpperCase()) // Type: string
}
}
Optional Chaining mit Narrowing
interface User {
address?: {
city: string
}
}
function getCity(user: User): string | undefined {
// Kein Narrowing nötig mit ?.
return user.address?.city
}
never Type für Exhaustiveness
function getArea(shape: Circle | Rectangle): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2
case "rectangle":
return shape.width * shape.height
default:
const _exhaustive: never = shape
return _exhaustive
}
}
// Wenn neuer Shape-Type hinzukommt, gibt's Error!
Häufige Fehler
Fehler 1: typeof für null
❌ FALSCH:
function isObject(value: unknown) {
return typeof value === "object"
}
isObject(null) // true! ⚠️
✅ RICHTIG:
function isObject(value: unknown) {
return typeof value === "object" && value !== null
}
Fehler 2: Type Assertion statt Type Guard
❌ UNSICHER:
function process(value: unknown) {
const str = value as string // Keine Runtime-Prüfung!
return str.toUpperCase() // Kann crashen!
}
✅ SICHER:
function process(value: unknown) {
if (typeof value === "string") {
return value.toUpperCase()
}
throw new Error("Not a string")
}
Fehler 3: Falsche Custom Type Guard
❌ FALSCH:
function isString(value: unknown): value is string {
return true // ⚠️ Immer true, aber nicht geprüft!
}
✅ RICHTIG:
function isString(value: unknown): value is string {
return typeof value === "string"
}
Zusammenfassung
Du hast gelernt:
- ✅ typeof für primitive Types
- ✅ instanceof für Class Instances
- ✅ in Operator für Properties
- ✅ Custom Type Guards mit
is - ✅ Discriminated Unions
- ✅ Control Flow Analysis
- ✅ Assertion Functions mit
asserts
Key Takeaways:
- Type Guards für sichere Runtime-Checks
- typeof für Primitives, instanceof für Classes
- Custom Type Guards mit
isPredicate - Discriminated Unions für Type Safety
- Assertion Functions für Guarantees
Nächste Schritte
Als Nächstes lernst du:
- TypeScript mit React
- Modules und Namespaces
- Advanced Types
- Best Practices
Viel Erfolg! 🚀
Gut gemacht! 🎉
Du hast "TypeScript Type Guards - Type Narrowing" abgeschlossen
Artikel bewerten
Bitte einloggen um zu bewerten
Das könnte dich auch interessieren
TypeScript Basic Types - Primitive Datentypen
Lerne die grundlegenden Datentypen in TypeScript kennen: string, number, boolean, arrays, tuples und mehr.
TypeScript Best Practices - Professioneller Code
Lerne die wichtigsten Best Practices für sauberen und wartbaren TypeScript Code. Naming, Patterns, Do's and Don'ts.
TypeScript Classes - Objektorientierte Programmierung
Lerne Classes in TypeScript mit Access Modifiers, Constructors, Inheritance, Abstract Classes und mehr.