はじめに
Flutterの「ウィジェットツリー」と「BuildContext」の概念について、
これらの裏側の仕組みを理解してコードを書くために、備忘録として書き記したいと思います。
本記事では、Flutterの根幹である $UI = f(state)$ の公式を軸に、その裏側を解説します。
1. Flutterの基本原則: UI = f(state)
FlutterのUI構築は、数学的な関数として表現できます。
$$UI = f(state)$$
- $state$: アプリが保持するデータ(変数、ログイン状態、APIからのレスポンスなど)
-
$f$:
Widget build(BuildContext context)の部分
データをどう見た目に変換するかの「ルール」 - $UI$: 画面上のピクセル
宣言的UIでは、「状態が変われば、関数 $f$ が再実行され、UIが再構築される」というシンプルな流れで画面が更新されます。
Riverpodの役割
この公式において、Riverpodは 「$state$ を管理し、$f$ へ適切に届けるデリバリー役」 です。
ref.watch(provider) を使うことは、「この $f$ は、この $state$ に依存しています」と宣言することを意味します。
2. 実は「3つのツリー」が動いている
「Flutterはウィジェットツリーでできている」と言われますが、内部では効率化のために3つの異なるツリーが並行して動いています。
-
Widget Tree
- 不変(Immutable)な構成
- 非常に軽量で、頻繁に作り直される
- ウィジェットの設計図であり、設定値(プロパティ)を設定する
-
Element Tree
- WidgetとRenderObjectを繋ぐオブジェクト
- ツリーの状態(State)を保持する
- BuildContext の正体はこの Element
- ウィジェットの実体
-
Render Tree
- 画面上のサイズ計算やレイアウト、描画を実際に担当する重いオブジェクト
なぜ再構築(Rebuild)しても速いのか?
$state$ が変わって $f$ が再実行されると、Widget Treeは新しく作り直されます。
しかし、Element Treeが新旧のWidgetを比較(Diffing)し、
「本当に変更があった部分」だけを Render Tree に伝えて描き直させる ため、高速な動作が維持されます。
3. BuildContext とは何か?
build メソッドで渡される context。
その正体は、前述の通り Element そのものです。
「そのウィジェットがツリーのどこにいるか」という位置情報(住所) であり、
言わば、家系図上の自分の位置を示すようなものです。
主な役割:ツリーを遡って情報を探す
ウィジェットは単体では設計図であり、ツリーを構成するコンポーネントパーツ(部品)のですが、context というハンドルを使うことで、ツリーの上位にある情報を探しに行けます。
-
Theme.of(context):親のテーマ情報を取得。 -
Navigator.of(context):ルート管理(住所を辿って遷移を依頼)。 -
MediaQuery.of(context):画面サイズを取得。
4. 再構築(Rebuild)の最適化
$UI = f(state)$ を効率化するために、コーディングの際に意識するポイントをまとめます。
① const コンストラクタを使う
const で宣言されたウィジェットは、親が再構築されても「内容が変わらないもの」と保証されるため、再計算が完全にスキップ されます。
最も手軽で強力な最適化です。
② Widgetの抽出(リファクタリング)
巨大な build メソッドは避けましょう。
小さなウィジェットに切り出すことで、再構築の範囲をそのウィジェット内に限定 できます。
③ Riverpodの select で監視を絞る
// 悪い例:User全体が変わるたびに再構築
final user = ref.watch(userProvider);
// 良い例:名前が変わった時だけ再構築
final name = ref.watch(userProvider.select((u) => u.name));
④ Async Gap への対策
非同期処理(await)の後に context を使う際は、必ず mounted チェックを行いましょう。
await someFuture();
if (!context.mounted) return; // 親ウィジェットが既に破棄されていないか確認
Navigator.of(context).pop();
まとめ
- Flutterは $UI = f(state)$ という関数である
- Widgetは設計図。裏側のElementとRenderObjectが効率化を担っている
- BuildContextは「ツリー内の住所」。上位の情報を引き出すために使う
- 最適化の基本は「再構築の範囲をいかに絞るか」にある
参考