1. ホーム
  2. flutter

[解決済み] Flutter 継承されたウィジェットを正しく使うには?

2022-05-11 12:55:58

質問

InheritedWidgetの正しい使用方法は何ですか?これまでのところ、ウィジェットツリーの下にデータを伝搬させる機会を与えるということを理解しました。極端な話、RootWidgetとして置くと、すべてのルートでツリー内のすべてのウィジェットからアクセスできるようになります。

しかし、InheritedWidgetは不変であり、どのようにそれを更新することができますか?そして、より重要なことは、どのように私のステートフルウィジェットは、そのサブツリーを再構築するためにトリガされるのですか?

残念ながら、このドキュメントは非常に不明瞭で、多くの人と議論した後、誰もそれを使用する正しい方法を本当に知らないようです。

Brian Eganからの引用を追加します。

はい、私はこれをツリーの下にデータを伝播させる方法だと考えています。私が見つけたもの API ドキュメントから、混乱しています。

継承されたウィジェットは、この方法で参照された場合、継承されたウィジェット自体の状態が変更されたときに この方法で参照すると、継承されたウィジェット自体が状態を変更したときに、コンシューマが再構築されます。

最初にこれを読んだとき、私は思いました。

InheritedWidgetにデータを詰め込んで、後で変異させればいいんだ。 変異が起こると、私のInheritedWidgetを参照しているすべてのWidgetが再構築されます。 私のInheritedWidgetを参照するすべてのWidgetを再構築します。 私が見つけたもの。

InheritedWidgetのStateを変異させるためには、それを の状態を変更するためには、それをStatefulWidgetでラップする必要があります。 StatefulWidgetの状態を実際に変異させて、このデータをInheritedWidgetに渡す。 は、そのデータをすべての子ウィジェットに渡します。しかし、この場合 を参照するウィジェットだけでなく、StatefulWidgetの下のツリー全体が再構築されるようです。 InheritedWidgetを参照するウィジェットだけでなく、StatefulWidgetの下のツリー全体が再構築されるようです。これは正しいのでしょうか? それとも、更新時に を参照するウィジェットをスキップする方法を知っていますか?

どのように解決するのですか?

この問題は、あなたの引用文に起因しており、それは正しくありません。

あなたが言ったように、InheritedWidgetsは、他のウィジェットのように、不変である。したがって、それらは を更新します。 . それらは新しく作成されます。

そのことは InheritedWidgetはデータを保持する以外には何もしないシンプルなウィジェットで . 更新のロジックも何も持っていません。 しかし、他のウィジェットのように、これは Element . そして何だと思いますか?これはミュータブルで、flutterは可能な限りこれを再利用します!

訂正後の引用文は:

に関連付けられたInheritedWidgetは、この方法で参照されると、コンシューマが再構築されることになります。 InheritedElement が変更されたときに再構築されます。

ウィジェット/エレメント/レンダーボックスがどのようにプラグインされているかについての素晴らしいトークがあります。 . しかし、簡単に言えば、それらは以下のようなものです(左が典型的なウィジェット、真ん中が「エレメント」、右が「レンダーボックス」) :

ということです。新しいウィジェットをインスタンス化するとき、flutter はそれを古いものと比較します。RenderBoxを指しているquot;Element"を再利用します。そして を変更します。 RenderBoxのプロパティを変更します。


なるほど、しかしこれが私の質問にどう答えるのでしょうか?

InheritedWidgetをインスタンス化した後 context.inheritedWidgetOfExactType (または MyClass.of でも基本的に同じです); 暗黙の了解として、このメソッドは Element に関連付けられた InheritedWidget . そして、その Element が新しいウィジェットを取得するたびに、前のメソッドを呼び出したすべてのウィジェットを強制的に更新します。

要するに、既存の InheritedWidget を新しいものに置き換えると、flutter はそれが変更されたことを認識します。そして、バインドされたウィジェットに変更の可能性を通知します。

すべてを理解したのであれば、すでに解決策を推測しているはずです。

をラップして InheritedWidget の中に StatefulWidget を作成すると、新しい InheritedWidget を作成します。

実際のコードでの最終結果は、:

class MyInherited extends StatefulWidget {
  static MyInheritedData of(BuildContext context) =>
      context.inheritFromWidgetOfExactType(MyInheritedData) as MyInheritedData;

  const MyInherited({Key key, this.child}) : super(key: key);

  final Widget child;

  @override
  _MyInheritedState createState() => _MyInheritedState();
}

class _MyInheritedState extends State<MyInherited> {
  String myField;

  void onMyFieldChange(String newValue) {
    setState(() {
      myField = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MyInheritedData(
      myField: myField,
      onMyFieldChange: onMyFieldChange,
      child: widget.child,
    );
  }
}

class MyInheritedData extends InheritedWidget {
  final String myField;
  final ValueChanged<String> onMyFieldChange;

  MyInheritedData({
    Key key,
    this.myField,
    this.onMyFieldChange,
    Widget child,
  }) : super(key: key, child: child);

  static MyInheritedData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedData>();
  }

  @override
  bool updateShouldNotify(MyInheritedData oldWidget) {
    return oldWidget.myField != myField ||
        oldWidget.onMyFieldChange != onMyFieldChange;
  }
}


しかし、新しい InheritedWidget を作成すると、ツリー全体が再構築されるのではないでしょうか?

いいえ、必ずしもそうなるわけではありません。新しい InheritedWidget は、以前とまったく同じ子を持つ可能性があるからです。正確には、同じインスタンスを意味します。 以前と同じインスタンスを持つウィジェットは、再構築されません。

そして最も多い状況(アプリのルートにinheritedWidgetを持つ)では、継承されたウィジェットは 一定 . そのため、不要な再構築はありません。