Fortgeschritten162025-01-15

TypeScript mit React - Typsichere Components

Lerne TypeScript in React zu verwenden. Props, State, Events, Hooks und mehr typisieren.

#typescript#react#components#hooks

TypeScript mit React - Typsichere Components

TypeScript und React sind das perfekte Team für typsichere UI-Entwicklung.

React Setup mit TypeScript

Neues Projekt

# Mit Create React App
npx create-react-app my-app --template typescript

# Mit Vite (empfohlen)
npm create vite@latest my-app -- --template react-ts

TypeScript zu bestehendem Projekt

npm install --save-dev typescript @types/react @types/react-dom

Function Components

Basic Component

interface ButtonProps {
  text: string
  onClick: () => void
}

function Button({ text, onClick }: ButtonProps) {
  return <button onClick={onClick}>{text}</button>
}

// Usage
<Button text="Click me" onClick={() => console.log("Clicked")} />

Mit React.FC (nicht empfohlen)

const Button: React.FC<ButtonProps> = ({ text, onClick }) => {
  return <button onClick={onClick}>{text}</button>
}

⚠️ Problem mit React.FC:

  • Implizites children Property
  • Generics werden kompliziert
  • Community-Empfehlung: Nicht verwenden!

Optional Props

interface ButtonProps {
  text: string
  onClick: () => void
  disabled?: boolean
  variant?: "primary" | "secondary"
}

function Button({
  text,
  onClick,
  disabled = false,
  variant = "primary"
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={variant}
    >
      {text}
    </button>
  )
}

Children Props

interface CardProps {
  title: string
  children: React.ReactNode
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </div>
  )
}

// Usage
<Card title="My Card">
  <p>Content here</p>
  <button>Action</button>
</Card>

Events

onClick Events

interface ButtonProps {
  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
}

function Button({ onClick }: ButtonProps) {
  return <button onClick={onClick}>Click</button>
}

// Usage
<Button onClick={(e) => {
  console.log(e.currentTarget) // HTMLButtonElement
}} />

onChange Events

interface InputProps {
  value: string
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
}

function Input({ value, onChange }: InputProps) {
  return <input value={value} onChange={onChange} />
}

// Usage
const [text, setText] = useState("")

<Input
  value={text}
  onChange={(e) => setText(e.target.value)}
/>

Form Events

interface FormProps {
  onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
}

function Form({ onSubmit }: FormProps) {
  return (
    <form onSubmit={onSubmit}>
      <input type="text" />
      <button type="submit">Submit</button>
    </form>
  )
}

// Usage
<Form onSubmit={(e) => {
  e.preventDefault()
  // Handle submit
}} />

Event Types Übersicht

React.MouseEvent<HTMLButtonElement>
React.ChangeEvent<HTMLInputElement>
React.ChangeEvent<HTMLTextAreaElement>
React.ChangeEvent<HTMLSelectElement>
React.FormEvent<HTMLFormElement>
React.KeyboardEvent<HTMLInputElement>
React.FocusEvent<HTMLInputElement>
React.ClipboardEvent<HTMLInputElement>

Hooks mit TypeScript

useState

// Type Inference
const [count, setCount] = useState(0) // Type: number

// Expliziter Type
const [user, setUser] = useState<User | null>(null)

// Mit initial undefined
const [data, setData] = useState<string>() // Type: string | undefined

// Array
const [items, setItems] = useState<string[]>([])

useEffect

useEffect(() => {
  // Effect Code
  console.log("Effect")

  // Cleanup
  return () => {
    console.log("Cleanup")
  }
}, []) // Dependencies

useRef

// DOM Element Ref
const inputRef = useRef<HTMLInputElement>(null)

useEffect(() => {
  inputRef.current?.focus()
}, [])

<input ref={inputRef} />

// Mutable Value Ref
const countRef = useRef<number>(0)

countRef.current += 1

useContext

interface AuthContext {
  user: User | null
  login: (email: string, password: string) => Promise<void>
  logout: () => void
}

const AuthContext = createContext<AuthContext | undefined>(undefined)

// Provider
function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)

  const login = async (email: string, password: string) => {
    // Login logic
  }

  const logout = () => {
    setUser(null)
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  )
}

// Hook
function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error("useAuth must be used within AuthProvider")
  }
  return context
}

Custom Hooks

interface UseFetchResult<T> {
  data: T | null
  loading: boolean
  error: Error | null
}

function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err)
        setLoading(false)
      })
  }, [url])

  return { data, loading, error }
}

// Usage
interface User {
  id: number
  name: string
}

const { data, loading, error } = useFetch<User>("/api/user/1")

Generic Components

interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => React.ReactNode
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

// Usage
interface User {
  id: number
  name: string
}

const users: User[] = [
  { id: 1, name: "Max" },
  { id: 2, name: "Anna" }
]

<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
/>

Component Props Types

Extending HTML Attributes

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "primary" | "secondary"
}

function Button({ variant = "primary", ...props }: ButtonProps) {
  return <button className={variant} {...props} />
}

// Alle standard button props verfügbar
<Button onClick={() => {}} disabled type="submit" />

Component Type

type ButtonComponent = React.ComponentType<ButtonProps>

const Button: ButtonComponent = ({ text, onClick }) => {
  return <button onClick={onClick}>{text}</button>
}
Real-World Form Component
interface FormData {
  email: string
  password: string
}

interface LoginFormProps {
  onSubmit: (data: FormData) => Promise<void>
  error?: string
}

function LoginForm({ onSubmit, error }: LoginFormProps) {
  const [formData, setFormData] = useState<FormData>({
    email: "",
    password: ""
  })
  const [loading, setLoading] = useState(false)

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setFormData(prev => ({
      ...prev,
      [name]: value
    }))
  }

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    setLoading(true)

    try {
      await onSubmit(formData)
    } finally {
      setLoading(false)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <div className="error">{error}</div>}

      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        disabled={loading}
      />

      <input
        type="password"
        name="password"
        value={formData.password}
        onChange={handleChange}
        disabled={loading}
      />

      <button type="submit" disabled={loading}>
        {loading ? "Loading..." : "Login"}
      </button>
    </form>
  )
}

// Usage
<LoginForm
  onSubmit={async (data) => {
    await api.login(data.email, data.password)
  }}
  error={loginError}
/>

📝 Quiz

Warum wird React.FC nicht mehr empfohlen?

Tipps & Tricks

ComponentProps Utility

import { ComponentProps } from "react"

// Extrahiere Props von existierenden Components
type ButtonProps = ComponentProps<"button">
type InputProps = ComponentProps<"input">

// Eigene Component
function MyButton(props: ComponentProps<"button">) {
  return <button {...props} />
}

forwardRef mit TypeScript

interface InputProps {
  label: string
}

const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label }, ref) => {
    return (
      <div>
        <label>{label}</label>
        <input ref={ref} />
      </div>
    )
  }
)

// Usage
const inputRef = useRef<HTMLInputElement>(null)
<Input label="Name" ref={inputRef} />

Render Props Pattern

interface MouseTrackerProps {
  render: (position: { x: number; y: number }) => React.ReactNode
}

function MouseTracker({ render }: MouseTrackerProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 })

  return (
    <div
      onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}
    >
      {render(position)}
    </div>
  )
}

// Usage
<MouseTracker
  render={({ x, y }) => <p>Mouse at {x}, {y}</p>}
/>

Häufige Fehler

Fehler 1: Event Type vergessen

FALSCH:

function Input({ onChange }: { onChange: (value: string) => void }) {
  return <input onChange={onChange} /> // ❌ Type Fehler
}

RICHTIG:

function Input({ onChange }: { onChange: (value: string) => void }) {
  return <input onChange={(e) => onChange(e.target.value)} />
}

Fehler 2: useState ohne Generic bei null

FALSCH:

const [user, setUser] = useState(null) // Type: null (fixiert!)

RICHTIG:

const [user, setUser] = useState<User | null>(null)

Fehler 3: useRef ohne null initial

FALSCH:

const ref = useRef<HTMLInputElement>() // Type: RefObject<HTMLInputElement | undefined>

RICHTIG:

const ref = useRef<HTMLInputElement>(null)
🎯

Zusammenfassung

Du hast gelernt:

  • ✅ Function Components mit Props Types
  • ✅ Event Types (onClick, onChange, etc.)
  • ✅ Hooks mit TypeScript (useState, useEffect, useRef)
  • ✅ Generic Components
  • ✅ Custom Hooks typisieren
  • ✅ Extending HTML Attributes
  • ✅ Context API mit TypeScript

Key Takeaways:

  • Keine React.FC verwenden
  • Event Types explizit angeben
  • useState mit Generic bei unions
  • useRef mit null initialisieren
  • Custom Hooks typisieren
  • ComponentProps Utility nutzen

Nächste Schritte

Als Nächstes lernst du:

  • TypeScript Best Practices
  • Advanced Patterns
  • Performance Optimizations
  • Testing mit TypeScript

Viel Erfolg! 🚀

TypeScriptLektion 11 von 15
73% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "TypeScript mit React - Typsichere Components" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten