🤔

Flutter Keyを理解するためにツリー構造を学ぶ

2021/09/13に公開

Flutter Keyがいまいち分からないので、ツリー構造について学んでみる

Flutterは入れ子ではない、ツリー構造だ

本稿、あるいは本シリーズはFlutter公式ドキュメントを参考に初学者が理解を深めるプログラミングノートである

FlutterのWidget同士の関係、構造は入れ子ではなく、ツリー構造になっているらしい。FlutterはWidgetが組み合わさることでツリー構造になるため、これを理解しないとコードのイメージがままならない。本稿ではこれをツリー構造やウィジェットツリーなどと呼ぶが、それらは基本的に同じものを指していると理解して読んでもらいたい。

具体的に述べると、Keyクラスがそれに当たる。Keyは親クラスを継承して子クラスを作る際のコンストラクタにKey? keyなどの記述が見られるが、そもそもKeyとはなんなのか分かっていない。以下、引用元の説明である。

簡単に言うと、ElementからWidgetを識別するためのIDです。Keyは意図的に指定しないとデフォルトではnullです。

必要となるシーンが限定される感じですが、よく紹介されているのは以下ですね。
ただ、必要性が分かるようで分からない感じで、何となく使えている感じがします。

ToDoアプリのようなStateをもったWidget郡のソート, 追加, 削除
Listのスクロール位置の保存
本記事を最後まで読んで頂くと理解できると思いますが、Widgetツリーの中でノード (あるWidget) を他の場所に移動させる場合など、Widgetツリーと対になるElementツリー側から特定のWidgetを識別する必要がある場合にのみKeyを利用します。

@kurun_pan Flutter WidgetにKeyが必要な理由, 仕組みについて https://qiita.com/kurun_pan/items/f91228cf5c793ec3f3cc 2021/09/12に引用

さらに、Keyについて調べてみると、FlutterにはElementやWidgetというFlutterのツリー構造にも関わる、根本的な仕組みが用意されており、Keyの概念や必要性などは、ここを理解しないと先に進めそうもない。

そのため、本稿ではKeyについて理解する前に、ツリー構造について学んでみる。

Flutterの根底にあるツリー構造を学ぶ

ツリー構造はその名前の通り、木のような形(ひっくり返したような形)をしており、Flutter上のUIウィジェットたちの組み合わさり方と言える。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

上記デモコードのツリー構造を図にしてみた(図の参考)。

childやchildrenに設定されたウィジェットはそれらの配下に置かれていくというイメージである。ちなみにデモコードにあるような、childやchildrenといった記述はプロパティと呼ぶ。それらにCenterやAppBarといったウィジェットを配置するといった感覚である。

例えば、HTMLとCSSであれば各要素を装飾していくといった感じだろう。もちろん、それらは大きな要素の中に要素が入っており、その中にもまた……。といった箱に詰めていく作業を思い浮かべるものだが、Flutterは箱というよりも、木に枝を生やすといったイメージを持つ。

Centerという枝(ウィジェット)は中央に生えている。そこから生えたTextという枝が中央寄せになっているのは当然のことで、いきなり横から生えてきたりはしない。また、他に中央寄せにしたいウィジェットがあれば、Centerウィジェットの配下に置く方がわかりやすい。

Flutterはこうしたウィジェットツリーを根底に自動で良い感じに描画してくれる。細かな調整はpaddingなどで行えばいい。

もちろん、childやchildrenプロパティにおけるウィジェットやその書き方については実際の開発で学んでいく必要があるだろう。

とりあえず、本稿ではプロパティを書き、そこに適したウィジェットを配置することでウィジェットツリーが形作られていくと認識する。

Elementが実体である

ここまで学んできたウィジェットツリーだが、これらは実際はElementというクラスと紐づいており、実際の関係性が構築されているようだ(参考)。

※私の理解が正しければ、開発者がウィジェットを配置すると、裏ではElementツリーが出来上がっており、親子ウィジェットを互いに参照し合うということ。

※また、このウィジェットツリーというものはイメージだそうで、実際にはElementクラスが働いているような感じだろうか。

参考文献を読んでみて思ったのは、FlutterはElementやそのツリー構造を意識させなくてもスラスラそれらしい見た目を作ることができる。

しかし、本稿の冒頭でも述べたような、Keyの理解にはElementやツリー構造といった理解が欠かせない。なぜ、Keyを使うのかといった根本的な理解はこれらの先にある気がする。

参考:
https://qiita.com/chooyan_eng/items/7c7298795f2db24b24a5

Discussion