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"
// 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! 🚀
Gut gemacht! 🎉
Du hast "TypeScript Best Practices - Professioneller Code" 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 Classes - Objektorientierte Programmierung
Lerne Classes in TypeScript mit Access Modifiers, Constructors, Inheritance, Abstract Classes und mehr.
TypeScript Enums - Benannte Konstanten
Lerne Enums in TypeScript für typsichere Konstanten. Numeric, String und Const Enums erklärt.