FlutterがソースコードからsetStateを分析するとどうなりましたか?

setStateの定義

State<T extends StatefulWidget> with Diagnosticableまず、このクラスで 定義されているsetStateの定義を見てみましょう。これはStatefulWidget、そのサブクラスの状態クラスです。メソッド本体には多くのコードがなく、ビジネスコードの実行時にいくつかの例外処理が行われます。主に次のように、特定のコードを投稿しません。

  • に渡されるsetState コールバックメソッドをnullにすることはできません。
  • ライフサイクルチェック:コンポーネントは dispose コンポーネントツリーから削除されると削除されるため、 dispose 後で呼び出す ことはできませんsetState通常、これはタイマー、アニメーション、または非同期コールバック中に発生します。このような呼び出しは、メモリリークにつながる可能性があります。
  • created フェーズおよびアンロードフェーズ(mounted) の間に 呼び出すことはできません。setStateつまり、コンストラクターで呼び出すことはできません setState通常、 のinitState 後に呼び出す 必要がありますsetState
  • setStateのコールバックメソッドはFutureオブジェクトを返すことができません。つまり、その setState中で非同期操作を実行することはできず、同期操作のみを実行できます。非同期操作を実行する場合は、 setState 外部で呼び出す必要があります。
@protected
void setState(VoidCallback fn) {
  // 省略异常处理代码
  _element!.markNeedsBuild();
}

コードの最も重要な行:_element!.markNeedsBuild()、関数名から、マークアップ要素を構築する必要があるということです。それで_element 、これはどこから来たのですか?掘り続けて!

Elementとは何ですか?

私たちが見ている_element 定義_element は オブジェクトです。実際、それを取得すると、それも返されることがStatefulElement わかりましたBuildContextを取得すると、注釈は次のようになります。BuildContext_element

このウィジェットが構築されるツリー内の場所-ウィジェットによって構築されるレンダリングツリーの特定の場所。

BuildContextStatefulElement は抽象クラスであるため、実際にはそのインターフェイス実装クラスまたはサブクラスである と推測できます 。ソースに戻ると、クラス階層全体が次のようになっていることがわかります。そのうち ElementComponentElement は抽象クラスであり、 markNeedsBuild メソッドは 抽象クラスでElement 定義されています。Elementの場合、公式の定義は次のとおりです。

ツリー内の特定の場所でのウィジェットのインスタンス化-レンダリングツリー内のウィジェットインスタンス化オブジェクト。

Element これは、 構成とレンダリングツリーを橋渡しするオブジェクトとして理解できますWidget 。つまり、実際のレンダリングプロセスは、ソースによって Element より制御されます。

 

上の図は、要素の主要なプロパティとメソッドを示しています。

  • _depth属性:コンポーネントツリー内の要素のレベル。ルートノードの値は0より大きくなければなりません。

  • _sort方法:2つのElement要素aとbのレベルを比較します。_depthレベル値()が大きいほど、レベルが深くなり、表示されるレベルが高くなります。

  • _parent:親ノード要素。空の場合があります。

  • _widget:構成要素のコンポーネント構成(実際には、 WidgetオブジェクトでWidget あり、実際にレンダリングされる要素ではなく、レンダリング要素の構成パラメーターです)。

  • _owner:要素宣言サイクルを管理するオブジェクト。

  • _lifecycleState:ライフサイクル状態プロパティ。デフォルトは initial 状態です。

  • 取得したメソッド: 返された要素とそのサブ要素でレンダリングする必要 renderObjectの あるオブジェクトが再帰的に呼び出されます(サブ要素はオブジェクトです)。getRenderObjectElement

  • reassembledebug 方法:再組み立て方法。ホットリロードなどの段階で のみ 使用され、呼び出されます。このメソッドは、必要に応じて要素自体のマーク付けを処理しbuild(メソッドを呼び出す markNeedsBuild )、すべての子ノードを再帰的にトラバースし、子ノードの reassemble メソッドを呼び出します。

  • updateChild:これはレンダリングプロセスのコアメソッドであり、指定された子要素を新しいコンポーネント構成で更新します。次の4つの組み合わせがあります。-空の場合 child と空で newWidget ない場合は、レンダリングする新しい要素が作成されます。

    • 空で child はないが空 newWidget の場合は、要素がコンポーネント構成 child に含まれていないことを意味するため、削除する必要があります。
    • 両方が空でない場合は、現在の要素を更新できるかどうかに応じ child て処理する必要があります(Widget.canUpdate)。更新できる場合は、新しいコンポーネント構成で要素を更新します。そうでない場合は、古い要素を削除して、で作成する必要があります。新しいコンポーネント構成新しい要素。
    • 両方が空の場合は、何もしません。

返される結果も3つのケースに分けられます。

   1. 如果创建了一个新的元素,则返回新构建的子元素。
   2. 如果旧的元素被更新,返回更新后的子元素。
   3. 如果子元素被移除,而没有新的替换的话,返回null。
   
  • mountメソッド:このメソッドは、新しい要素が初めて作成されたときに呼び出され、要素は指定された挿入位置(スロット)に従って指定された親ノードに挿入されます。このメソッドを呼び出した後、要素の状態はから initial に 変わりますactiveこれにより、子要素のレベル(_depth)が親要素のレベル+1に設定されます。
  • updatenewWidgetメソッド:このメソッドは、親ノードが 新しい構成コンポーネント()で要素を変更したときに呼び出されます。新しい構成タイプは、古い構成タイプと同じである必要があります。
  • detachRenderObjectおよび attachRenderObject:それぞれ、コンポーネントツリーからRenderObjectを削除および追加します。
  • deactivateChild方法:非アクティブな要素のリストに子要素を追加してから、レンダリングツリーから削除します。
  • activate方法:状態が非アクティブからアクティブに切り替わるときに呼び出されます。これはライフサイクル機能に属します。このメソッドは、コンポーネントが初めてマウントされるときに呼び出されるのではなく、mountメソッドが呼び出されることに注意してください。
  • deactivate メソッド:状態がアクティブから非アクティブに切り替わるとき、つまり要素が非アクティブリストに移動されたときに呼び出されます。
  • unmount メソッド:状態が非アクティブ状態から無効(存在しない)状態に切り替わるときに呼び出されます。その時点で、要素はレンダリングツリーを離れ、レンダリングツリーに存在しなくなります。
  • didChangeDependencies:要素の依存関係が変更されたときに呼び出されます。このメソッドはメソッドも呼び出します markNeedBuild 。
  • markNeedsBuildアプローチ: dirty 次のフレームがレンダリングされたときに要素を再構築できるように、要素を状態としてマークします。この方法の中核は、次のことを行うことです。
_dirty = true;
owner!.scheduleBuildFor(this)
  • rebuild メソッド:要素の BuildOwner オブジェクトが scheduleBuildFor メソッドを呼び出すと、要素を再構築するためにメソッドが呼び出さ rebuild れます。これは、最初にロードされたときに mount メソッドでトリガーされ、構成コンポーネントが変更されたときにメソッド でトリガーされますbuild 。このメソッドは、 performRebuildメソッドを呼び出して要素を再構築します。performRebuildこれは、Elementの単語クラスによって実装されるメソッドです。つまり、各要素がどのように再構築されるかは、サブクラスによって決定されます。

コンテンツはよく見えます。要素のライフサイクルの状態図であるレンダリングの状態フローを見てみましょう。コンポーネントは、要素が非アクティブな要素のリストに移動されたときにdeactivate メソッドをトリガー するメソッドから削除され ます。deactivate要素を非アクティブリストに移動する方法はdeactivateChild、親ノードでの操作です。子要素が親によって構築されたレンダリングツリーの一部でなくなると、非アクティブ要素リストに追加されます。

 

performRebuild方法

performRebuild setStateの場合、コンポーネントツリーを再構築するためにメソッドが実際に呼び出されることが わかったので、 メソッドは何をしperformRebuild ますか?Elementでは、performRebuildメソッドは空のメソッドであり、サブクラスで実装する必要があります。それでは、StatefulElementに移動して、コードを確認しましょう。コードは次のとおりです。

@override
void performRebuild() {
  if (_didChangeDependencies) {
    state.didChangeDependencies();
    _didChangeDependencies = false;
  }
  super.performRebuild();
}

私は見上げる必要があります、そしてそれは ComponentElement それです、私はついにそれを見つけました!

@override
void performRebuild() {
  // 省略调试的代码
  Widget? built;
  try {
    // ...
    built = build();
    // ...
  } catch (e, stack) {
    // ...
  } finally {
    // We delay marking the element as clean until after calling build() so
    // that attempts to markNeedsBuild() during build() will be ignored.
    _dirty = false;
    // ...
  }
  try {
    _child = updateChild(_child, built, slot);
    assert(_child != null);
  } catch (e, stack) {
    // 省略异常处理
  }
  // 省略调试代码
}

ここで重要なのは、 build メソッドとupdateChild メソッドが呼び出されることです。その中で built = build()、最新のものを取得することWidgetにより、buildメソッドはコンポーネント構成を再構築するため、対応するウィジェットのコンストラクターとbuildメソッドが呼び出されます。次に、メソッドを呼び出し updateChildて子要素を更新します。前述のように、updateChild サブコンポーネントの更新には3つの組み合わせがあります。そして、ここでの合計は_child 間違い builtなく空ではありません。重要なのは、  built (Widget オブジェクト) canUpdate がであるかどうかです trueこのメソッドは、Widgetクラスで定義されています。

static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType &&
      oldWidget.key == newWidget.key;
}

Widget 設定されてい ない場合 key (通常、コンポーネントのキーを設定することはお勧めしません)、  runtimeType 2つのコンポーネントの整合性を更新できることに注意してください。したがって、実際には、ほとんどの場合に返されるのはです trueコードをデバッグおよび更新した場合の結果は同じであり、最終的にはElementyes のこのブランチになりますupdateChild 。

// ...
else if (hasSameSuperclass &&
          Widget.canUpdate(child.widget, newWidget)) {
  if (child.slot != newSlot) updateSlotForChild(child, newSlot);
  child.update(newWidget);
  assert(child.widget == newWidget);
  assert(() {
    child.owner!._debugElementWasRebuilt(child);
    return true;
  }());
  newChild = child;
}

setState このことから、メソッド呼び出しは実際に全体 Widgetを再構築するが、必ずしも Widget 構成 されたElement要素ツリーのすべての要素を削除し、それを新しい要素に置き換えて再レンダリングするとは限らないと推測できます。Flutter 実際、デバッグ中にデバッグツールが開いたことがわかります。ボタンをクリックしても、 実際のWidget 対応 Element は変更されません。

要約する

 呼び出しは、レイヤーのようにレンダーコントロールレイヤーのそのレイヤーの  すべてを再構築するsetStateわけではありません ただし、これは  使用に問題がないことを意味するわけではありません。まず、前の章で説明したように、  ツリー全体が再構築され、パフォーマンスが低下します。次に、  ツリー全体が変更されたため、ツリー全体のレンダリングはに対応します。レイヤーオブジェクトは メソッドを実行しますが、再レンダリングされない場合がありますが、ツリー全体をトラバースするパフォーマンスのオーバーヘッドも高くなります。したがって、パフォーマンスの観点からは、使用しないようにしてください。 ただし、このコンポーネントが本当に単純で、従属コンポーネントがないか、ほとんどない場合を除きます。WidgetElementelementsetStateWidgetWidgetElementupdatesetState

おすすめ

転載: blog.csdn.net/jdsjlzx/article/details/123320732