const、キー、そして照合のおかげで無駄な再構築をなくし、それからDevToolsで計測しましょう。
このレッスンを Kodokon で開くFlutterは三つのツリーを扱います。ウィジェット(不変で使い捨ての構成)、Element(状態を運ぶ生きたインスタンス)、そしてレンダーオブジェクト(レイアウト、描画)です。ウィジェットの再構築は安上がりです。コストがかかるのはレイアウトと連鎖する描画です。再構築のあいだ、Element.updateChildは三つのルールを順番に適用します。新しいウィジェットが古いものと同一(同じインスタンス)なら、サブツリー全体がショートカットされます。Widget.canUpdateが真なら(同じruntimeType、同じkey)、Elementは保持され、単に再構成されます。そうでなければ、Elementは破棄され、それから状態も含めて再展開されます。constキーワードは最初のルールを活用します。定数式はコンパイラによって正規化され、ビルドごとに同じインスタンスが返り、identicalが真になって、枝全体がスキップされます。
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('Increment'),
),
],
);
}
}
class _Header extends StatelessWidget {
const _Header();
@override
Widget build(BuildContext context) {
debugPrint('build _Header');
return const Text('Counter');
}
}子のリストでは、照合はまず位置によってペアを組みます。状態を持つ要素をキーなしで並べ替えると、各Elementはその位置の前の住人の状態を保ってしまいます。チェックボックスが間違った場所でチェックされ、テキストフィールドが入れ替わります。Keyはペアの組み方のルールを変えます。アルゴリズムはリストの中で移動しても、同じキーを持つElementを見つけます。安定した業務上の識別子にValueKeyを使いましょう。GlobalKeyはさらに進んで、状態を失うことなくサブツリー全体を親付け替えできるようにします。ただし本物のコスト(グローバルなレジストリ、ツリー全体にわたる一意性の制約)を伴うので、Formや親付け替えのような本当に必要な場面のためにとっておきましょう。
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)),
),
],
);
}
}最適化する前に計測しましょう。DevToolsでは、Performanceビューが二つの重要なスレッドのフレームごとの時間を追跡します。UI(あなたのDartコード。ビルド、レイアウト)とラスター(レイヤーのラスタライズ)です。予算は60 Hzで約16 msです。ラスター側のジャンクは、ビルドを最適化しても直りません。描画のコスト(影、saveLayer、大きすぎる画像)から来るものだからです。誰が再構築しているかを見るにはTrack widget buildsを有効にし、再描画された領域を可視化するにはdebugRepaintRainbowEnabledを有効にしましょう。再描画のたびに枠の色が変わります。RepaintBoundaryはサブツリーを独自のレイヤーへと隔離します。絶え間なく再描画するスピナーが、もう隣人を汚さなくなります。
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(),
),
),
),
);
}
}