Fortgeschritten142025-01-15

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#best-practices#clean-code#patterns

TypeScript Best Practices - Professioneller Code

Best Practices für sauberen, wartbaren und typsicheren TypeScript Code.

tsconfig.json Konfiguration

Strict Mode aktivieren

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

Warum strict?

  • Findet mehr Bugs zur Compile-Time
  • Erzwingt besseren Code
  • Langfristig weniger Fehler

Empfohlene Additional Checks

{
  "compilerOptions": {
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true
  }
}

Type vs Interface

Interfaces für Objekte

// ✅ Gut
interface User {
  id: number
  name: string
  email: string
}

// ✅ Erweiterbar
interface Admin extends User {
  permissions: string[]
}

Types für Unions/Intersections

// ✅ Gut
type Status = "active" | "inactive" | "pending"
type ID = string | number

// ✅ Gut für Intersection
type TimestampedUser = User & {
  createdAt: Date
  updatedAt: Date
}

Regel

// Interface: Für Objekt-Strukturen
interface Config {
  apiUrl: string
  timeout: number
}

// Type: Für alles andere
type Callback = (data: string) => void
type Result = Success | Error

any vermeiden

❌ Schlecht

function process(data: any) {
  return data.value // Kein Type-Checking!
}

✅ Besser: unknown

function process(data: unknown) {
  if (typeof data === "object" && data !== null && "value" in data) {
    return (data as { value: string }).value
  }
  throw new Error("Invalid data")
}

✅ Am Besten: Proper Types

interface Data {
  value: string
}

function process(data: Data) {
  return data.value
}

Type Assertions sparsam nutzen

❌ Schlecht

const user = getUserData() as User // Keine Runtime-Prüfung!
console.log(user.name) // Kann crashen

✅ Besser: Type Guards

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value &&
    typeof value.name === "string"
  )
}

const userData = getUserData()
if (isUser(userData)) {
  console.log(userData.name) // Sicher!
}

Null/Undefined Handling

❌ Schlecht

function getUser(): User | null {
  // ...
}

const user = getUser()
console.log(user!.name) // ! ist gefährlich

✅ Besser: Null Checks

const user = getUser()
if (user) {
  console.log(user.name)
}

✅ Optional Chaining

const cityName = user?.address?.city ?? "Unknown"

Immutability

readonly für Arrays

// ❌ Schlecht
interface Config {
  features: string[]
}

const config: Config = { features: ["auth", "api"] }
config.features.push("new") // Funktioniert, aber änderbar

// ✅ Besser
interface Config {
  readonly features: readonly string[]
}

const config: Config = { features: ["auth", "api"] }
config.features.push("new") // ❌ Error

as const für Literals

// ❌ Type: string[]
const colors = ["red", "green", "blue"]

// ✅ Type: readonly ["red", "green", "blue"]
const colors = ["red", "green", "blue"] as const

Function Overloads

Wann nötig

// ✅ Gut - unterschiedliche Return Types
function get(id: number): User
function get(email: string): User | null
function get(identifier: number | string): User | null {
  // Implementation
}

Wann nicht nötig

// ❌ Übertrieben
function greet(name: string): string
function greet(name: string, greeting: string): string
function greet(name: string, greeting?: string): string {
  return `${greeting || "Hello"} ${name}`
}

// ✅ Besser - einfacher mit Optional
function greet(name: string, greeting = "Hello"): string {
  return `${greeting} ${name}`
}

Generics Best Practices

Aussagekräftige Namen

// ❌ Schlecht
function map<T, U, V>(arr: T[], fn: (item: T) => U): V[] {
  // V macht hier keinen Sinn
}

// ✅ Besser
function map<TInput, TOutput>(
  arr: TInput[],
  fn: (item: TInput) => TOutput
): TOutput[] {
  return arr.map(fn)
}

Constraints nutzen

// ❌ Zu offen
function getProperty<T>(obj: T, key: string) {
  return obj[key] // ❌ Error
}

// ✅ Mit Constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

Error Handling

Result Type Pattern

type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E }

async function fetchUser(id: number): Promise<Result<User>> {
  try {
    const user = await api.get(`/users/${id}`)
    return { success: true, data: user }
  } catch (error) {
    return { success: false, error: error as Error }
  }
}

// Usage
const result = await fetchUser(1)
if (result.success) {
  console.log(result.data.name)
} else {
  console.error(result.error.message)
}

Naming Conventions

Interfaces & Types

// ✅ Gut - beschreibende Namen
interface User {}
interface UserProfile {}
type UserId = string

// ❌ Schlecht - I-Prefix (C# Style)
interface IUser {}

// ❌ Schlecht - T-Prefix
type TUser = {}

Boolean Variables

// ✅ Gut
const isActive = true
const hasPermission = false
const canEdit = true
const shouldUpdate = false

// ❌ Schlecht
const active = true
const permission = false

Functions

// ✅ Gut - Verben
function getUser() {}
function createOrder() {}
function validateEmail() {}

// ❌ Schlecht - Nomen
function user() {}
function order() {}

Utility Types nutzen

interface User {
  id: number
  name: string
  email: string
  password: string
}

// ✅ Gut - Utility Types
type PublicUser = Omit<User, "password">
type PartialUser = Partial<User>
type ReadonlyUser = Readonly<User>

// ❌ Schlecht - manuell duplizieren
interface PublicUser {
  id: number
  name: string
  email: string
}

Code Organization

Barrel Exports

// user/index.ts
export * from "./types"
export * from "./api"
export * from "./hooks"

// Usage
import { User, useUser, fetchUser } from "./user"

Type Files

// types/user.ts
export interface User {
  id: number
  name: string
}

export type UserId = number
export type UserRole = "admin" | "user"

// api/user.ts
import type { User, UserId } from "../types/user"
Real-World Best Practices Beispiel
// types.ts
export interface User {
  readonly id: number
  name: string
  email: string
  role: "admin" | "user"
  createdAt: Date
}

export interface CreateUserDto {
  name: string
  email: string
  password: string
}

export type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E }

// api.ts
import type { User, CreateUserDto, Result } from "./types"

export async function createUser(
  dto: CreateUserDto
): Promise<Result<User>> {
  try {
    const response = await fetch("/api/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(dto)
    })

    if (!response.ok) {
      return {
        success: false,
        error: new Error(`HTTP ${response.status}`)
      }
    }

    const user = await response.json()
    return { success: true, data: user }
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error : new Error(String(error))
    }
  }
}

// hooks.ts
import { useState } from "react"
import type { User } from "./types"
import { createUser } from "./api"

export function useUserCreation() {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const create = async (name: string, email: string, password: string) => {
    setLoading(true)
    setError(null)

    const result = await createUser({ name, email, password })

    if (result.success) {
      return result.data
    } else {
      setError(result.error.message)
      return null
    }
  }

  return { create, loading, error }
}

📝 Quiz

Was ist der Vorteil von 'strict: true' in tsconfig.json?

Tipps & Tricks

ESLint mit TypeScript

// .eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/explicit-function-return-type": "warn"
  }
}

VS Code Settings

{
  "typescript.suggest.autoImports": true,
  "typescript.updateImportsOnFileMove.enabled": "always",
  "editor.codeActionsOnSave": {
    "source.organizeImports": true
  }
}

Path Aliases

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

// Usage
import { Button } from "@components/Button"

Häufige Fehler

Fehler 1: Type Pollution

SCHLECHT:

// types in jedem File
export interface User { ... }
export interface Product { ... }
export interface Order { ... }

BESSER:

// types/index.ts - zentrale Type Definitionen
export interface User { ... }
export interface Product { ... }
export interface Order { ... }

Fehler 2: Zu viele Type Assertions

SCHLECHT:

const user = data as User
const id = user.id as number
const name = user.name as string

BESSER:

// Proper Types von Anfang an
function getUser(): User {
  // ...
}

Fehler 3: any bei externen Libraries

SCHLECHT:

import someLib from "some-lib" // any

someLib.doSomething() // Kein Type-Checking

BESSER:

npm install @types/some-lib

// Oder eigene Types
declare module "some-lib" {
  export function doSomething(x: number): string
}
🎯

Zusammenfassung

Du hast gelernt:

  • ✅ strict Mode aktivieren
  • ✅ Interface für Objekte, Type für Unions
  • ✅ any vermeiden, unknown nutzen
  • ✅ Type Guards statt Type Assertions
  • ✅ Immutability mit readonly
  • ✅ Result Type für Error Handling
  • ✅ Naming Conventions
  • ✅ Code Organization

Key Takeaways:

  • strict: true ist Pflicht
  • unknown > any
  • Type Guards > Type Assertions
  • Utility Types nutzen
  • Barrel Exports für Organisation
  • ESLint mit TypeScript
  • Type Definitions zentral

Nächste Schritte

Jetzt kannst du:

  • ✅ Professionellen TypeScript Code schreiben
  • ✅ Best Practices anwenden
  • ✅ Code Organisation verbessern
  • ✅ Type Safety maximieren

Weiter lernen:

  • Decorators
  • Advanced Patterns
  • Testing mit TypeScript
  • Performance Optimization

Viel Erfolg! 🚀

TypeScriptLektion 12 von 15
80% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "TypeScript Best Practices - Professioneller Code" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten