LoginSignup
15
14

More than 3 years have passed since last update.

Flutterの3つのツリー(Widgetツリー/Elementツリー/RenderObjectツリー)をできるだけわかりやすくまとめてみた

Posted at

:book: Flutterの記事を整理し本にしました :book:

  • 本稿の記事を含む様々な記事を体系的に整理し本にまとめました
  • 今後はこちらを最新化するため、最新情報はこちらをご確認くださ
  • 10万文字を超える超大作になっています(笑)

はじめに

FlutterでElementというものを度々目にするようになって、その後調べていったら、芋づる式に3つのツリーを勉強することになりました。
結構納得したので、整理してみました。

まとめ


本記事は、下記のGoogleDeveloperDaysの情報をベースとしています。

3つのツリー

FlutterではWidgetのツリーで要素が構成されるという話をしてきましたが、Widgetツリーがそのまま画面に表示されているわけではありません。

実は、3つのツリーが相互に連携しあうことで、画面の構築、状態管理、性能などを最適化しています。

pic1.png

それぞれの詳細説明に入る前に、なぜ3つに分けているのかと、大まかな役割について解説してきます。

3つのツリーに分離する目的

3つのツリーに分ける目的は、端的にいうとパフォーマンスの最適化です。
FlutterはWidgetで管理されており、頻繁に再構成され、なおかつそれぞれのWidgetは状態を持っています。
また、画面に表示するためには、実際のサイズやレイアウトを意識した調節も必要になってきます。

仮に1つのツリーで管理されている場合は、管理はわかりやすいかもしれません。
しかし、全体がツリー構造のため、根本に近いWidgetが変わった場合は、その子孫のWidgetをことごとく作り直しをして、レンダリングする必要があります。
Flutterでは、Widgetは大きなネスト構造となるため、これは性能劣化を引き起こします。

そこで、3つのツリーに機能を分け、それぞれの役割と責任範囲を整理することで、これらの問題を解決しています。

3つのツリーの概要

Widgetツリー

Widgetツリーはimmutable(不変)なオブジェクトで構成されています。
Widgetツリーは生成と破棄のコストが非常に小さいように設計されています。つまり、頻繁に作り変えられるWidgetツリーの更新に瞬時に対応できるように設計されています。

Elementツリー

Elementツリーはmutable(可変)なオブジェクトで構成されています。
Widgetツリーはimmutableなので状態を持ちません。
そのため、ElementはWidgetツリーに変わって状態を管理します。また、RenderObjectのライフサイクルについても管理します。
いうなれば、Widgetツリーに状態をもたらし、WidgetツリーとRenderObjectツリーの仲介する役割です。

RenderObjectツリー

RenderObjectツリーはmutable(可変)なオブジェクトで構成されています。
RenderObjectは画面のレンダリングと描画を担当します。
Flutterが実際に画面に表示する際に参照するのは、Widgetツリーではなく、このRenderObjectツリーです。

イメージで整理すると以下のとおりです。
pic2.png

WidgetツリーとElementツリーの関係

WidgetツリーとElementツリーの対応

WidgetツリーとElementツリーは1対1に対応した要素を持っています。
これだと完全に冗長にみえるのですが、後述する状態管理において、意味合いが変わってきます。

pic3.png

状態管理

繰り返しになりますが、Widgetはimmutableで高速に生成と破棄されるため、状態を持ちません。
しかし、StatefulWidgetでsetStateをすると値が書き換わります。
これはElementツリーがその状態を管理しているため、実現できています。
StateObjectというものを作成し、状態を作り管理します。

pic4.png

状態の更新

setStateで値を変えた場合に何が起こるでしょうか。
setStateをすると、StateObjectがdirtyとなり変更の必要があることを認識します。その後、StateObjectが最新化され、Elementに対応するWidgetがbuildされ、再作成されるという流れになります。
この時、Elementツリーは再作成のコストが高いので、可能なものは極力再利用します。

pic5.png

WidgetはcanUpdateなどのメソッドをつかい、新旧のWidgetのtypeとkeyを使って、別物に変わったかなどをチェックしています。
また、UpdateRenderObjectメソッドでは、既存と同じものはそのままに、変わったところだけを更新するという、極力再利用する方針の処理が行われます。

ツリー構築

構築

HelloWorldを例に説明していきます。
HelloWorldでは、Centerの中にTextが入っているので、途中を省略すると、Centerが親でTextが子供の構造が作られます。
その後、Elementツリーが作られ、対応するRenderObjectツリーが順次作られていきます。

pic6.png

更新

次に、更新処理を見ていきます。
半分は、WidgetツリーとElementツリーの場所で見たものと同じです。StateObjectは省略しています。

pic7.png

Widgetツリーは瞬時に新しいオブジェクトに作り変えられ、それを受けて、参照先の変更と変更された分の要素の変更が伝播していきます。
ElementツリーとRenderObjectツリーは必要な限り再利用するので、Widgetツリーと異なり再作成されていないことがポイントです。

アプリの一番最初で行うrunAppメソッドは、Widgetツリーの根本にそのWidgetを配置するという操作をしています。

確認

  • HelloWorldでDevToolを使って確認してみます。 ボタンをクリックし、カウンタが増えてもRenderObjectIDが変わっていないことがわかるかと思います。

pic8.png

より現実的な例

より現実的に性能の問題などを考える場合は、同じツリー構造を持った者同士を切り替えられるかという点になってきます。

上述の通り、ElementツリーとRenderObjectツリーは可能な限り再作成を行わず、修正コストを小さく抑えようとしますので、下図のような、大きく内容が変わる場合であっても、同じWidgetツリー構造を持ってさえいれば、書き換えは最小限で済むように設計されているため、非常に高速であるといえます。

pic9.png

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14