React kennt zwei Arten von Komponenten: funktionale Komponenten und Klassen-Komponenten. Bis 2019 (Einführung der Hooks) war die Klassen-Variante notwendig, sobald eine Komponente State oder Lifecycle-Methoden brauchte. Seitdem sind funktionale Komponenten der Standard – Klassen-Komponenten begegnen dir vor allem in Bestandscode. Dieser Artikel zeigt die Unterschiede, vergleicht typische Lifecycle-Muster und führt durch eine Mini-Migration.
Funktionale Komponente
Eine funktionale Komponente ist eine JavaScript-Funktion, die JSX zurückgibt:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Geklickt: {count}
</button>
);
}State, Effekte, Refs und Context werden über Hooks verfügbar gemacht. Kein this, keine bind-Aufrufe, keine besondere Klassen-Syntax.
Klassen-Komponente
Klassen-Komponenten leiten von React.Component ab und nutzen this.state, this.setState und Lifecycle-Methoden:
import { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={this.handleClick}>
Geklickt: {this.state.count}
</button>
);
}
}Auffallend: Mehr Boilerplate, manuelles bind() (sonst fehlt der this-Kontext), zwei Stellen für Initialisierung (constructor) und Render-Logik (render).
Direkter Vergleich
| Aspekt | Funktional + Hooks | Klassen-Komponente |
|---|---|---|
| Syntax | Funktion | Klasse mit render()-Methode |
| State | useState, useReducer | this.state, this.setState |
| Side Effects | useEffect | componentDidMount etc. |
| Refs | useRef | createRef, Callback-Refs |
this-Binding | Nicht nötig | Manuell oder Class-Property |
| Logik-Wiederverwendung | Custom Hooks | HOCs, Render Props |
| Performance-Memoizing | React.memo | PureComponent, shouldUpdate |
| Lesbarkeit Lifecycle | Linear | Verteilt auf mehrere Methoden |
| Standard heute | + | Bestandscode |
Lifecycle-Vergleich
Klassen-Komponenten haben fest definierte Lifecycle-Methoden. In funktionalen Komponenten erledigt useEffect denselben Job – mit anderem Modell.
class Page extends Component {
componentDidMount() {
document.title = 'Seite geladen';
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.fetchData(this.props.id);
}
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() { return <div>...</div>; }
}function Page({ id }) {
useEffect(() => {
document.title = 'Seite geladen';
}, []);
useEffect(() => {
fetchData(id);
}, [id]);
useEffect(() => {
const timer = setInterval(...);
return () => clearInterval(timer);
}, []);
return <div>...</div>;
}Vorteil der Hooks-Variante: Logik, die zusammengehört, steht zusammen. In der Klasse ist sie über componentDidMount, componentDidUpdate und componentWillUnmount verteilt.
Mini-Migration: Klasse -> Funktion
Ausgangspunkt: Eine Class-Component mit Datenladen.
class UserProfile extends Component {
state = { user: null, loading: true };
componentDidMount() {
fetch(`/api/users/${this.props.id}`)
.then((res) => res.json())
.then((user) => this.setState({ user, loading: false }));
}
render() {
if (this.state.loading) return <p>Lädt...</p>;
return <h1>{this.state.user.name}</h1>;
}
}Migration in drei Schritten:
class ... extends Component->functionthis.state->useStatecomponentDidMount->useEffect(() => {...}, [])
import { useEffect, useState } from 'react';
function UserProfile({ id }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${id}`)
.then((res) => res.json())
.then((u) => {
setUser(u);
setLoading(false);
});
}, [id]);
if (loading) return <p>Lädt...</p>;
return <h1>{user.name}</h1>;
}Realer Code würde Fehlerbehandlung und Cleanup ergänzen – das Prinzip bleibt aber identisch.
Wann sind Klassen-Komponenten heute noch sinnvoll?
In neuen Projekten: nie. In Bestandscode: so lange, wie sie funktionieren. Eine Pauschal-Migration nur „weil moderner" lohnt sich selten – Klassen-Komponenten werden auf absehbare Zeit unterstützt.
Ein praktisch relevanter Fall: Error Boundaries. Diese gibt es bis heute nur als Klassen-Komponente, weil dafür componentDidCatch und getDerivedStateFromError benötigt werden. In der Praxis wickelt man das einmal in eine Wrapper-Klasse oder nutzt die Bibliothek react-error-boundary und arbeitet darüber wieder mit funktionalen Komponenten.
Häufige Stolperfallen beim Umstieg
this in Hooks suchen.
Existiert nicht. Werte werden direkt mit useState/useRef gehalten.
setState synchron erwarten.
War in Klassen schon falsch, ist in Hooks ebenfalls falsch. Setter-Aufrufe wirken nach dem Re-Render.
Mehrere useEffect-Aufrufe als „Performance-Problem" sehen.
Im Gegenteil: Pro Verantwortung ein Effekt ist klarer als ein einziger großer Effekt mit mehreren if-Verzweigungen.
Class-Component vergessen zu unmounten.
Hooks-Cleanup (return () => {...} in useEffect) ersetzt componentWillUnmount. Wer den Cleanup vergisst, hat denselben Memory-Leak wie zuvor.