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
childrenProperty - 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>
}
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! 🚀
Gut gemacht! 🎉
Du hast "TypeScript mit React - Typsichere Components" 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.