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

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

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

---

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

## まとめ
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)


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

https://www.youtube.com/watch?v=996ZgFRENMs&t=149s

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

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


![pic1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/7cc94a14-2e4f-932f-72b2-e53968ea0cf3.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/60226cc7-0610-241f-5bc4-63dc5d8d6414.png)


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

![pic3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/bb0bcd07-c44a-816c-d531-13d21f11bb5c.png)


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

![pic4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/3859ff07-a4d0-a216-9e42-5c633559811e.png)


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

![pic5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/7a4df8e7-46a4-70e1-ca6e-cc854feb30ae.png)

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

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

![pic6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/b891d868-a992-715f-de9d-e9b55feaa5af.png)


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

![pic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/ba7656ad-864d-f97e-fab9-4347c304f1e2.png)


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


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

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

![pic8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/2240b961-c049-ec21-2fd4-08539d0945f5.png)


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

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

![pic9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/115fd22c-bd7c-7f4c-4efa-1d0ded9cc5c5.png)