Éliminez les rebuilds inutiles grâce à const, aux clés et à la réconciliation, puis mesurez avec DevTools.
Ouvrir cette leçon dans KodokonFlutter 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.
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');
}
}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.
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)),
),
],
);
}
}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.
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(),
),
),
),
);
}
}