🦧

markNeedsBuildとは?

2022/02/05に公開

Element「再構築、必要、マークつけとくから頼んだ🙋‍♂️」

Elementが「再構築(rebuild)が必要(Needs)になりました🙋‍♂️」とrebuildを希望します。

rebuildを希望するElementは自身にマーク(mark)をつける決まりになっています。
(ここからは再構築とrebuildはrebuildで統一して表現します)

https://api.flutter.dev/flutter/widgets/Element/markNeedsBuild.html

解説していきます。

マークとは具体的に何?

=> Elementが持つpropertyのdirty(bool)のことです
https://api.flutter.dev/flutter/widgets/Element-class.html#:~:text=read-only-,dirty,-→ bool

dirtyとは?

=> Elementの持つpropertyで型はboolです。

要素が再構築が必要であるとマークされている場合はtrueを返します。(google翻訳)
https://api.flutter.dev/flutter/widgets/Element/dirty.html

雰囲気を実際のコードで伝えます

class Element {
    //...
    /// Returns true if the element has been marked as needing rebuilding.
    bool get dirty => _dirty;
    bool _dirty = true;
    //....
}

正確なコードはこちら
https://github.com/flutter/flutter/blob/57d522287686792964545692862ba3128b572b61/packages/flutter/lib/src/widgets/framework.dart#L4402

ここまでがmarkNeedsBuildの簡単な説明です。


Q&A形式で理解を深める

ここからはmarkNeedsBuildが
flutterの他の機能とどのように関わり
何を実現するかなどをQ&A形式で書いていきます

rebuildは誰に要請するの?

=> BuildOwnerクラスです。

BuildOwnerクラスとは何?

=> Elementツリーを管理するクラスです

rebuildはBuildOwnerクラスのどこでされるの?

=> BuildOwnerのbuildScopeメソッドです

フレーム毎に呼ばれるdrawFrameがbuildScopeを呼び出すようです。

markNeedsBuildのメソッドの中身は?何をしているの?

=> dirtyをtrueにしBuildOwnerのクラスのscheduleBuildForメソッドを呼びます。引数は自身(Element)

まずBuildOwnerのクラスのscheduleBuildForメソッドの説明をします

=> _dirtyElementsというprivateなpropertyに引数として渡されたElementを追加します
_dirtyElementsとはrebuild待ちElementリストのことです。

(まとめます)markNeedsBuildのメソッドの中身は?何をしているの?

ElementのmarkNeedsBuildメソッドの処理の流れを見てみましょう

1,ElementクラスのmarkNeedsBuildメソッドは
BuildOwnerクラスのscheduleBuildForメソッドを呼び出します。
引数として自身(Element)を渡します。

2,呼び出されたBuildOwnerクラスのscheduleBuildForメソッドは渡されたElementを
rebuild待ちElementリストである_dirtyElementsプロパティに追加します。

少々複雑な説明になってしまったので
最低限必要な部分のみを切り出した簡単なコードを見てみましょう

Element

//色々と省略...
void markNeedsBuild() {
 //色々と省略...
 _dirty = true;  
 owner!.scheduleBuildFor(this); //1
}

BuildOwner

//色々と省略...
final List<Element> _dirtyElements = <Element>[];
void scheduleBuildFor(Element element) {
//色々と省略...
_dirtyElements.add(element); //2
}

その_dirtyElementsはその後どのように使われるの?

=> フレーム毎に呼ばれるWidgetBindingdrawFrameがBuildOwnerのbuildScopeを呼び出しrebuild待ちElementリストである_dirtyElementsを順に取り出しrebuildしていきます

buildScopeのコードを見てみる(抜粋し4行にした)

framework.dart
for (final Element element in _dirtyElements) {
//...
 try {
        element.rebuild();
      } catch (e, stack) {
        // ...
      }
//...
}

気になる方はgithubで確認してみて下さい。

https://github.com/flutter/flutter/blob/fd8d035921574d69a789201f5f707f94971b6edc/packages/flutter/lib/src/widgets/framework.dart#L2759


追記

flutterのrebuildは全てこの手順で行われるの?

そんなことはありません。
例えばStatelessElementはElementのupdateをoverrideして
rebuildをしています。

Widgetは再生成コストが低い => 積極的に再生成すべし
Elementは再生成コストが高い => パフォーマンスに悪影響を及ぼす。一度他の方法がないか見直すべし。
こんな感じの説明は至る所で見るかと思いますが
”Widgetを再生成しElementは使い回す”
をとてもわかりやすいコードで実装してくれている良い例です
内部のコードを直接読むことに抵抗がある人の0.1歩として良いと思います
何かのきっかけになれば。


void update(StatelessWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  _dirty = true;
  rebuild();
}

https://github.com/flutter/flutter/blob/ba01ec8faae9a9be3cd90ee2b7357a803b7a7178/packages/flutter/lib/src/widgets/framework.dart#L4863

Discussion