Flutter StatelessWidget vs StatefulWidget: compréhension approfondie
Comprendre la différence entre StatelessWidget et StatefulWidget est fondamental pour le développement Flutter.
Cette section analysera en profondeur les scénarios d'utilisation et les mécanismes de fonctionnement internes de ces deux types de Widgets.
Différence essentielle
Dans Flutter, il existe deux principaux types de Widgets, et leur différence fondamentale réside dans leur capacité à contenir un état mutable.
| Fonctionnalités | StatelessWidget | StatefulWidget |
|---|---|---|
| State | Immuable | mutable |
| Moment de reconstruction | Uniquement lorsque le Widget parent se reconstruit | Lorsque l'état change + le Widget parent se reconstruit |
| Performance | Plus élevé (pas besoin d'écouter l'état) | Légèrement plus faible (nécessite une gestion d'état) |
| Cas d'utilisation | Contenu statique | Contenu interactif dynamique |
Explication détaillée de StatelessWidget
StatelessWidget convient au contenu qui ne change pas au fil du temps. Une fois créé, toutes ses propriétés sont finales et ne peuvent pas être modifiées.
Cas d'utilisation
- Afficher du texte statique, des images, des icônes
- Affichage d'une liste de données (la source de données provient du Widget parent)
- Conteneurs de mise en page sans interaction utilisateur
- Sections d'affichage en lecture seule dans les formulaires
Exemple : Exemple complet de StatelessWidget
// Composant d'affichage des informations utilisateur (sans état)
class UserCard extends StatelessWidget {
// Les propriétés sont définies comme final pour garantir l'immuabilité
final String name;
final String email;
final String avatarUrl;
// Le constructeur const améliore les performances
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),
// Informations utilisateur
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Texte(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Texte(
email,
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
],
),
),
);
}
}
Explication détaillée de StatefulWidget
StatefulWidget convient aux scénarios qui nécessitent de répondre aux interactions de l'utilisateur ou aux changements de données. Il se compose de deux parties : le Widget lui-même (immuable) et l'objet State (mutable).
Cas d'utilisation
- Données dynamiques telles que les compteurs, les minuteurs
- Saisie de formulaire
- Contrôle d'animation
- Affichage de l'état des requêtes réseau
- Réponse aux interactions utilisateur
Cycle de vie de l'objet State
| Méthodes | Description |
|---|---|
| initState | Appelé lorsque le Widget est créé pour la première fois, utilisé pour initialiser l'état. |
| didChangeDependencies | Appelé lorsque les dépendances du Widget changent. |
| build | Construit l'interface utilisateur, appelé à chaque changement d'état. |
| didUpdateWidget | Appelé lorsque le Widget parent se reconstruit |
| dispose | Appelé lorsque le Widget est supprimé, utilisé pour nettoyer les ressources. |
Exemple : Exemple complet de StatefulWidget
// Application compteur
class CounterApp extends StatefulWidget {
const CounterApp({super.key});
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
// État du compteur
int _counter = 0;
// Timer (pour démontrer dispose)
Timer? _timer;
// 1. initState - Initialiser l'état
@override
void initState() {
super.initState();
// Initialiser le compteur
_counter = 0;
// Démarrer un minuteur (incrémenter chaque seconde)
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_increment();
});
}
// 2. didChangeDependencies - Appelé lorsque les dépendances changent
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
// 3. build - Construire l'interface utilisateur
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Compteur'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Nombre actuel :',
style: TextStyle(fontSize: 20),
),
Texte(
'$_counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
],
),
),
// Bouton d'incrémentation
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
// Méthode pour incrémenter le compteur
void _increment() {
// Il faut appeler setState pour déclencher la mise à jour de l'interface
setState(() {
_counter++;
});
}
// 4. didUpdateWidget - Appelé lorsque le widget parent se reconstruit
@override
void didUpdateWidget(CounterApp oldWidget) {
super.didUpdateWidget(oldWidget);
}
// 5. dispose - Nettoyer les ressources
@override
void dispose() {
// Annuler le timer pour éviter les fuites de mémoire
_timer?.cancel();
super.dispose();
}
}
Utilisation correcte de setState
Dans StatefulWidget, vous devez notifier Flutter des changements d'état via setState.
Utilisation correcte
Exemple : Utilisation correcte de setState
void _increment() {
setState(() {
_counter++;
});
}
// Correct : lorsque la mise à jour de l'état n'est pas liée à l'UI, vous n'avez pas besoin de setState
void _someInternalLogic() {
// Logique interne, n'affecte pas l'interface utilisateur
_calculateSomething();
}
// Correct : Mettre à jour l'état après une opération asynchrone
Future<void> _loadData() async {
final data = await fetchData();
setState(() {
_items = data;
});
}
Utilisation incorrecte
Exemple : Utilisation incorrecte de setState
void _increment() {
_counter++; // L'état a changé, mais l'interface utilisateur ne se mettra pas à jour !
}
// Erreur : Appeler setState dans la méthode build
@override
Widget build(BuildContext context) {
// Ne jamais appeler setState dans build !
setState(() {
_counter++; // Cela conduit à une boucle infinie
});
return Text('$_counter');
}
setState déclenchera un nouvel appel de la méthode build lors du prochain rendu de frame.
Des appels fréquents à setState ou l'appel de setState à l'intérieur de la méthode build peuvent tous deux entraîner des problèmes de performance.
Comment choisir
Pendant le développement, vous devez choisir le type de Widget approprié en fonction des besoins réels :
Choisissez StatelessWidget lorsque
- L'apparence du Widget ne dépend d'aucun état mutable.
- Toutes les données requises sont transmises depuis le Widget parent via le constructeur.
- Le Widget n'a pas besoin de répondre aux interactions de l'utilisateur.
- Viser des performances optimales
Choisissez StatefulWidget lorsque
- Le Widget doit maintenir un état interne.
- Doit répondre aux entrées de l'utilisateur (clics, glissements, etc.)
- Doit répondre aux modifications de données (par ex., réponses aux requêtes réseau)
- Besoin de contrôler les animations