key の使い時 - Flutter ウィジェット 101(初級編)Ep.4 - Youtube
Google公式動画の書き起こしです。
最初は動画を見て理解した気になっても後からなんだっけ?となることが多いので大分雑ですが文字にしておきます。
参考にする場合は動画を見たあとでざーっと見て「こうだったな〜」と復習するが良いと思います(じゃないとこの記事が何言ってるのかわからないので...)。
keyとは
- Widgetが動き回る時にステートを保持するもの
- 具体的にはユーザーのスクロールの位置や、コレクションの変更時のステートを保持する
When(どんな時使うの?)
実は使い所は多くないけど…
何かのステートを保持するような類似したWidgetのコレクションを追加、削除、ソートするときはkeyが使える
例えばTODOアプリでは項目の追加、削除、ソートとか
HackerNewsアプリではリストビューが再作成された際に、ニュースWidgetが誤って古いステートを取得するのを防いでいる
Statelessなものには使わないがStetefulllなものにはステートを保持させたいので使う
Where(どこで使うの?)
動画では「エレメントツリー」「参考順序」は後で流れで説明していますが先に書いておきます。
エレメントツリー
- エレメントツリーというのは、Flutterのアプリの骨格となるもの
- アプリの構造を示すだけのもので、その他の追加情報は、もとのWidgetを参照することで得る
参照順序
- 行方向に並んでいるWidgetの順序を入れ替える場合、Flutterはエレメントツリーを調べ、骨格構造を照合する
- 親であるRowのエレメントを調べて、その後、子となるStatelessWidgetを調べてく。
- そのエレメントツリーでは、入れ替えた後の新しいWidgetが同じ型かどうか(動画の場合はTile)、keyが古いかどうかを調べる
- もしそうであれれば、新しいWidgetに更新する。
この場合はStatelessでkeyを持っていないのでエレメントツリーの型のみを調べることになる
Stateless
Stetelessなウィジェットの場合、Rowウィジェットは子供に対して秩序あるスロットを持っている
FlutterではどのWidgetにも対応するエレメントをビルドする。
エレメントツリー自体はシンプルで、各Widgetの型と、子エレメントへの参照に関する情報のみを保持している。
Statefull(key無し)
Statefullでkeyをもっていない場合はどうなるのか
今回はStatefuleなTileWidgetは一組のステートオブジェクトを持っている
これはWidget自体にではなく、そのエレメントが持っているもので、そこに色の情報が格納されている
そして、2つのWidgetを入れ替えると、Flutterはエレメントツリーを得調べ、RowWidgetの型を照合
その後参照先を更新することになる。
次に、Tileエレメント(Tile Widgetではない)が対応しているWidgetを調べ同じ型(Tile)か照合する
今回はTileエレメントがTileウィジェットを指しているのでここはOK。それを子が順番に行う。
Flutterはエレメントツリーとそれに対応するステートを使うことで、デバイス上に表示する内容を判断する。
よって表面上ではウィジェットが適切に入れ替わらなかったように見えた。
Statefull(key有り)
Widget TreeにあるWidgetにkeyを追加
これによってエレメントツリーもkeyを持つことになる
ここでWidgetを入れ替えると、Widgetの型は一致しているが、対応するWidgetのkeyと一致しなくなる
そこで、Flutterがこれらのエレメントを解除する(エレメントツリーからウィジェットツリーに対して引かれている矢印を一旦解除)
エレメントツリーの中のタイルエレメントに参照を移し、一致してない無い最初のものから調査する。
するとFlutterが一致してない子を調べ対応するkeyを伴うエレメントがあるかを照合する。
一致していれば対応Widgetにその参照を更新する(エレメントツリーからウィジェットツリーに対して矢印を引く)。
2つ目の子エレメントも同様に参照先を更新していく
これによって想定された内容を表示していた
要するにkeyはコレクションの中の主要ウィジェットの順序を入れ替えたり、数を変更する時に便利!
ステートではアニメーションの再生や、ユーザが入力したデータ、スクロール位置もステートが関わってくる。
Which one(最適なkeyはどれ?)
では、どこに配置したら良いのか。
答えは、「保持する必要があるウィジェットサブツリーの最上部」
皆さんはそれをStatefullWidgetだとお思いでしょうか?
それは間違っています。
Statefullなウィジェットではない例
例えば、StatefullなWidgetをPaddingで囲むとする。
子の位置をウィジェットツリー側で入れ替えるとエレメントとウィジェットを照合するFlutterのアルゴリズムは、ツリー階層ごとに照合する。
第一階層の子(Padding)では、全て(今回は型のみ)が一致している。
第二階層では、Tileエレメントのkeyと、Tileウィジェットのkeyが不一致であることをFlutterが把握し、接続を解除する。
この例で使用しているkeyはローカルのkey。
つまり、ウィジェットとエレメントの一致を照合する際に、Flutterはツリー内の特定レベルの中で一致するkeyだけを探す。
第2階層では、そのkeyの値を伴うTileエレメントを見つけられなかったので、新しいものを作成し、新しいステートを初期化してしまった(今回の場合、色はランダムなので、新しい色のタイルが作成された)
対策
第一階層(Paddingウィジェット)に対してkeyを設定する。
keyの種類
Flutterの公式ドキュメントを見てみるといろいろなkeyがある。
さて、どれを使おう?
ウィジェットのコレクションを変更する場合は、他の子のkey間で区別するだけ。
子ウィジェットの中に保管している情報を見てみると、使うべきウィジェットがわかる。
ValueKey
アイテムのテキストは一定かつ固有であることが求められる場合
例)
ToDoアプリのアイテムのテキストは一定かつ固有であることが求められる。
そんな時には、テキストが値であるValueKeyを使用する
ObjectKey
それぞれの子ウィジェットが複雑なデータの組み合わせを保管している場合
つまり、別の入力データと同じである可能性があるが、組み合わせは固有である場合
例)
例えば、ユーザ情報をリスト化したアドレス帳アプリ。
名前や誕生日など、個々のフィールドは、別の入力データと同じである可能性があるが、組み合わせは固有。
UniqueKey
コレクションに同じ値の複数のウィジェットがある場合や、各ウィジェットを他と明示的に区別したい場合。
例)
タイル交換アプリでは、タイルに保存している一定のデータが他になく、ウィジェットを構築するまで色がわからないためUniqueKeyを使用した。
keyの中で無作為の数を使用するのはアンチパターン。
ウィジェットが作成されるたびに新しい無作為の数のkeyが生成され、一貫性を失ってしまう。
PageStorageKey
スクロール位置の保存に特化したkey。
これによってページに帰ってきた時に前までスクロールしていた位置をデフォルトで表示出来る。
GlobalKey
アプリ内の階層を問わずステートを保ったまま、ウィジェットに親を変更させることが出来る。
または、ウィジェットツリーの全く別の場所にある、別のウィジェットにアクセスするために使用することも出来る。
例)
2つの異なる画面で同じウィジェットを表示して、ステートを全て同じに保つ場合に使用する。
他には、パスワードを認証してもそのツリー内の別のウィジェットとステート情報を共有したくない場合。
最後に
ステートを調べる良い方法として、継承したウィジェットかredux、BLoCパターンを使用できる。
つまり、ウィジェットツリー全域でステートを保持したい時keyを使用する。
これは同じ型のウィジェットのコレクションを変更する時に起こる。
保持したいウィジェットツリーの最上部にkeyを配置して、そのウィジェットで保存するデータ型に基づいて、使用するkeyを選択しましょう。