Fortgeschritten122025-01-15

TypeScript Type Guards - Type Narrowing

Lerne Type Guards für sichere Runtime Type Checks. typeof, instanceof, Custom Type Guards und mehr.

#typescript#type-guards#narrowing#runtime

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
}
Real-World API Response Handler
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 is Predicate
  • 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! 🚀

TypeScriptLektion 10 von 15
67% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "TypeScript Type Guards - Type Narrowing" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten