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ésStatelessWidgetStatefulWidget
StateImmuablemutable
Moment de reconstructionUniquement lorsque le Widget parent se reconstruitLorsque l'état change + le Widget parent se reconstruit
PerformancePlus élevé (pas besoin d'écouter l'état)Légèrement plus faible (nécessite une gestion d'état)
Cas d'utilisationContenu statiqueContenu 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

import 'package:flutter/material.dart';

// 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éthodesDescription
initStateAppelé lorsque le Widget est créé pour la première fois, utilisé pour initialiser l'état.
didChangeDependenciesAppelé lorsque les dépendances du Widget changent.
buildConstruit l'interface utilisateur, appelé à chaque changement d'état.
didUpdateWidgetAppelé lorsque le Widget parent se reconstruit
disposeAppelé lorsque le Widget est supprimé, utilisé pour nettoyer les ressources.

Exemple : Exemple complet de StatefulWidget

import 'package:flutter/material.dart';

// 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

// Correct : mettre à jour l'état dans 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

// Erreur : Modification de l'état sans appeler 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
autres extensions