Kodokon kodokon.com

Performance : const, rebuilds, clés et DevTools

Éliminez les rebuilds inutiles grâce à const, aux clés et à la réconciliation, puis mesurez avec DevTools.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

Flutter manipule trois arbres : les widgets (configurations immuables, jetables), les Element (instances vivantes qui portent l'état) et les render objects (layout, peinture). Reconstruire un widget est bon marché ; ce qui coûte, c'est le layout et la peinture en cascade. Lors d'un rebuild, Element.updateChild applique trois règles dans l'ordre : si le nouveau widget est identique (même instance) à l'ancien, tout le sous-arbre est court-circuité ; si Widget.canUpdate est vrai (même runtimeType, même key), l'Element est conservé et simplement reconfiguré ; sinon, l'Element est détruit puis regonflé, état compris. Le mot-clé const exploite la première règle : une expression constante est canonisée par le compilateur, la même instance revient à chaque build, identical est vrai - et la branche entière est sautée.

DART
import 'package:flutter/material.dart';

class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() =>
      _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const _Header(),
        Text('Total : $_count'),
        FilledButton(
          onPressed: () => setState(() => _count++),
          child: const Text('Incrémenter'),
        ),
      ],
    );
  }
}

class _Header extends StatelessWidget {
  const _Header();

  @override
  Widget build(BuildContext context) {
    debugPrint('build _Header');
    return const Text('Compteur');
  }
}
Le log ne s'affiche qu'une fois : le const saute le sous-arbre.

Pour les listes d'enfants, la réconciliation apparie d'abord par position. Réordonnez des items stateful sans clés et chaque Element garde l'état de l'ancien occupant de sa position : cases cochées au mauvais endroit, champs de texte mélangés. Une Key change la règle d'appariement : l'algorithme retrouve l'Element portant la même clé, même déplacé dans la liste. Utilisez ValueKey sur un identifiant métier stable. GlobalKey va plus loin - elle permet de réparenter un sous-arbre entier sans perdre son état - mais elle a un coût réel (registre global, contrainte d'unicité sur tout l'arbre) : réservez-la aux vrais besoins comme Form ou le réparentage.

DART
import 'package:flutter/material.dart';

class TodoList extends StatelessWidget {
  const TodoList({super.key, required this.items});

  final List<String> items;

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        for (final item in items)
          Dismissible(
            key: ValueKey(item),
            onDismissed: (direction) {},
            child: ListTile(title: Text(item)),
          ),
      ],
    );
  }
}
Sans clé stable, Dismissible lève une erreur dès la suppression.

Mesurez avant d'optimiser. Dans DevTools, la vue Performance trace le temps par frame des deux threads critiques : UI (votre code Dart : build, layout) et raster (la rastérisation des layers). Le budget est d'environ 16 ms à 60 Hz. Un jank côté raster ne se corrige pas en optimisant vos builds : il vient du coût de peinture (ombres, saveLayer, images surdimensionnées). Activez Track widget builds pour voir qui se reconstruit, et debugRepaintRainbowEnabled pour visualiser les zones repeintes - le liseré change de couleur à chaque repaint. RepaintBoundary isole un sous-arbre dans son propre layer : un spinner qui se repeint en continu ne salit plus ses voisins.

DART
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  debugRepaintRainbowEnabled = true;
  runApp(const DemoApp());
}

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      showPerformanceOverlay: true,
      home: Scaffold(
        body: Center(
          child: RepaintBoundary(
            child: CircularProgressIndicator(),
          ),
        ),
      ),
    );
  }
}
Overlay de frames + arc-en-ciel de repaint : le duo de diagnostic.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Pourquoi un widget const est-il ignoré lors du rebuild de son parent ?
    • L'instance est canonique : identical est vrai, et Element.updateChild court-circuite tout le sous-arbre
    • Le compilateur supprime le widget de l'arbre en release
    • Les widgets const n'ont pas de méthode build
  2. Vous réordonnez une liste de widgets stateful sans clés. Que se passe-t-il ?
    • Les Elements sont appariés par position : chaque item récupère l'état de l'ancien occupant de sa position
    • Flutter lève une exception en mode debug
    • L'état suit automatiquement chaque widget déplacé
    • La liste refuse de se reconstruire
  3. Que mesurent les deux graphes de l'overlay de performance ?
    • Le temps par frame du thread UI (code Dart) et du thread raster (rendu des layers)
    • La mémoire allouée et le nombre de widgets
    • Le temps de build et le temps de garbage collection