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:

TypeScript Funktionale Komponente
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:

TypeScript Klassen-Komponente
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

AspektFunktional + HooksKlassen-Komponente
SyntaxFunktionKlasse mit render()-Methode
StateuseState, useReducerthis.state, this.setState
Side EffectsuseEffectcomponentDidMount etc.
RefsuseRefcreateRef, Callback-Refs
this-BindingNicht nötigManuell oder Class-Property
Logik-WiederverwendungCustom HooksHOCs, Render Props
Performance-MemoizingReact.memoPureComponent, shouldUpdate
Lesbarkeit LifecycleLinearVerteilt 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.

TypeScript Klassen-Lifecycle
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>; }
}
TypeScript Hooks-Äquivalent
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.

TypeScript Vorher (Class)
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:

  1. class ... extends Component -> function
  2. this.state -> useState
  3. componentDidMount -> useEffect(() => {...}, [])
TypeScript Nachher (Hooks)
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.

/ Weiter

Zurück zu Components

Zur Übersicht