Flutter StatelessWidget vs StatefulWidget: Tiefgehendes Verständnis
Das Verständnis des Unterschieds zwischen StatelessWidget und StatefulWidget ist grundlegend für die Flutter-Entwicklung.
Dieser Abschnitt analysiert eingehend die Einsatzszenarien und internen Funktionsmechanismen dieser beiden Widget-Typen.
Kernunterschied
In Flutter gibt es zwei Haupttypen von Widgets, und ihr grundlegender Unterschied liegt darin, ob sie veränderlichen Zustand halten können.
| Funktionen | StatelessWidget | StatefulWidget |
|---|---|---|
| State | Unveränderlich | mutable |
| Zeitpunkt des Neuaufbaus | Nur wenn das Parent-Widget neu aufgebaut wird | Wenn sich der Zustand ändert + das übergeordnete Widget neu erstellt wird |
| Leistung | Höher (kein Überwachen des Zustands nötig) | Etwas niedriger (erfordert Zustandsverwaltung) |
| Anwendungsfall | Statischer Inhalt | Dynamischer interaktiver Inhalt |
Detaillierte Erklärung von StatelessWidget
StatelessWidget eignet sich für Inhalte, die sich im Laufe der Zeit nicht ändern. Nach der Erstellung sind alle seine Eigenschaften final und können nicht geändert werden.
Anwendungsfall
- Anzeige von statischem Text, Bildern und Symbolen
- Anzeige einer Datenliste (Datenquelle stammt vom übergeordneten Widget)
- Layout-Container ohne Benutzerinteraktion
- Schreibgeschützte Anzeigebereiche in Formularen
Beispiel: Vollständiges StatelessWidget-Beispiel
// Benutzerinfo-Anzeigekomponente (zustandslos)
class UserCard extends StatelessWidget {
// Eigenschaften werden als final definiert, um Unveränderlichkeit zu gewährleisten
final String name;
final String email;
final String avatarUrl;
// const-Konstruktor verbessert die Leistung
const UserCard({
super.key,
required this.name,
required this.email,
this.avatarUrl = '',
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// Avatar
CircleAvatar(
backgroundImage: avatarUrl.isNotEmpty
? NetworkImage(avatarUrl)
: null,
child: avatarUrl.isEmpty
? const Icon(Icons.person)
: null,
),
const SizedBox(width: 16),
// Benutzerinfo
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
email,
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
],
),
),
);
}
}
StatefulWidget Detaillierte Erklärung
StatefulWidget eignet sich für Szenarien, die auf Benutzerinteraktionen oder Datenänderungen reagieren müssen. Es besteht aus zwei Teilen: dem Widget selbst (unveränderlich) und dem State-Objekt (veränderbar).
Anwendungsfall
- Dynamische Daten wie Zähler, Timer
- Formulareingabe
- Animationssteuerung
- Anzeige des Status von Netzwerkanfragen
- Reaktion auf Benutzerinteraktion
Lebenszyklus des State-Objekts
| Methoden | Beschreibung |
|---|---|
| initState | Wird aufgerufen, wenn das Widget erstmals erstellt wird, und dient zur Initialisierung des States. |
| didChangeDependencies | Wird aufgerufen, wenn sich die Abhängigkeiten des Widgets ändern. |
| build | Erstellt die UI, wird jedes Mal aufgerufen, wenn sich der State ändert. |
| didUpdateWidget | Wird aufgerufen, wenn das übergeordnete Widget neu erstellt wird |
| dispose | Wird aufgerufen, wenn das Widget entfernt wird, und dient zum Freigeben von Ressourcen. |
Beispiel: Vollständiges StatefulWidget-Beispiel
// Zähler-App
class CounterApp extends StatefulWidget {
const CounterApp({super.key});
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
// Zählerzustand
int _counter = 0;
// Timer (um dispose zu demonstrieren)
Timer? _timer;
// 1. initState - State initialisieren
@override
void initState() {
super.initState();
// Zähler initialisieren
_counter = 0;
// Einen Timer starten (jede Sekunde inkrementieren)
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_increment();
});
}
// 2. didChangeDependencies - Wird aufgerufen, wenn sich Abhängigkeiten ändern
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
// 3. build - UI erstellen
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Zähler'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Aktueller Zählerstand:',
style: TextStyle(fontSize: 20),
),
Text(
'$_counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
],
),
),
// Schaltfläche zum Erhöhen
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
// Methode zum Inkrementieren des Zählers
void _increment() {
// setState muss aufgerufen werden, um die UI zu aktualisieren
setState(() {
_counter++;
});
}
// 4. didUpdateWidget - Wird aufgerufen, wenn das übergeordnete Widget neu erstellt wird
@override
void didUpdateWidget(CounterApp oldWidget) {
super.didUpdateWidget(oldWidget);
}
// 5. dispose - Ressourcen aufräumen
@override
void dispose() {
// Timer abbrechen, um Speicherlecks zu verhindern
_timer?.cancel();
super.dispose();
}
}
Korrekte Verwendung von setState
In einem StatefulWidget müssen Sie Flutter über Zustandsänderungen mithilfe von setState benachrichtigen.
Korrekte Verwendung
Beispiel: Korrekte Verwendung von setState
void _increment() {
setState(() {
_counter++;
});
}
// Richtig: Wenn die Zustandsaktualisierung nichts mit der UI zu tun hat, benötigen Sie setState nicht
void _someInternalLogic() {
// Interne Logik, hat keinen Einfluss auf die UI
_calculateSomething();
}
// Richtig: Zustand nach asynchroner Operation aktualisieren
Future<void> _loadData() async {
final data = await fetchData();
setState(() {
_items = data;
});
}
Falsche Verwendung
Beispiel: Falsche Verwendung von setState
void _increment() {
_counter++; // Der Zustand hat sich geändert, aber die UI wird nicht aktualisiert!
}
// Falsch: setState in der build-Methode aufrufen
@override
Widget build(BuildContext context) {
// Rufe niemals setState in build auf!
setState(() {
_counter++; // Dies führt zu einer Endlosschleife
});
return Text('$_counter');
}
setState löst beim Rendern des nächsten Frames einen erneuten Aufruf der build-Methode aus.
Häufige Aufrufe von setState oder der Aufruf von setState innerhalb der build-Methode können beide zu Leistungsproblemen führen.
Wie man wählt
Während der Entwicklung sollten Sie den passenden Widget-Typ basierend auf den tatsächlichen Anforderungen auswählen:
Wählen Sie StatelessWidget, wenn
- Das Erscheinungsbild des Widgets hängt von keinem veränderlichen Zustand ab.
- Alle erforderlichen Daten werden vom übergeordneten Widget über den Konstruktor übergeben.
- Das Widget muss nicht auf Benutzerinteraktionen reagieren.
- Wenn optimale Leistung angestrebt wird
Wählen Sie StatefulWidget, wenn
- Das Widget muss einen internen Zustand verwalten.
- Muss auf Benutzereingaben (Klicks, Wischgesten usw.) reagieren.
- Muss auf Datenänderungen reagieren (z. B. Antworten auf Netzwerkanfragen)
- Muss Animationen steuern