151 votes

Flutter : Comment utiliser correctement un widget hérité ?

Quelle est la bonne façon d'utiliser un InheritedWidget ? Jusqu'à présent, j'ai compris qu'il vous donne la possibilité de propager des données dans l'arbre des widgets. A l'extrême, si vous le placez en tant que RootWidget, il sera accessible à partir de tous les Widgets de l'arbre sur toutes les Routes, ce qui est bien parce que d'une manière ou d'une autre, je dois rendre mon ViewModel/Model accessible à mes Widgets sans avoir à recourir à des globales ou Singletons.

MAIS InheritedWidget est immuable, alors comment puis-je le mettre à jour ? Et, plus important encore, comment mes widgets à états peuvent-ils être déclenchés pour reconstruire leurs sous-arbres ?

Malheureusement, la documentation est ici très peu claire et, après avoir discuté avec beaucoup de personnes, personne ne semble vraiment savoir quelle est la bonne façon de l'utiliser.

J'ajoute une citation de Brian Egan :

Oui, je le vois comme un moyen de propager les données vers le bas de l'arbre. Ce que je trouve confus, dans la documentation de l'API :

"Les widgets hérités, lorsqu'ils sont référencés de cette manière, font en sorte que le consommateur à reconstruire lorsque le widget hérité lui-même change d'état".

Quand j'ai lu ça la première fois, j'ai pensé :

Je pourrais mettre des données dans l'InheritedWidget et les modifier plus tard. Lorsque cette mutation se produira, elle reconstruira tous les Widgets qui font référence à mon InheritedWidget. font référence à mon InheritedWidget Ce que j'ai trouvé :

Afin de modifier l'état d'un InheritedWidget, vous devez l'envelopper dans un StatefulWidget. dans un StatefulWidget. Ensuite, vous modifiez réellement l'état du StatefulWidget et vous transmettez ces données au InheritedWidget, lequel transmet les données à tous ses enfants. Cependant, dans ce cas, il semble reconstruire toute l'arborescence sous le StatefulWidget, et pas seulement les pas seulement les widgets qui font référence au InheritedWidget. Est-ce correct ? Ou est-ce qu'il saura d'une manière ou d'une autre comment ignorer les Widgets qui font référence au InheritedWidget si updateShouldNotify renvoie false ?

2 votes

Excellente question ! Merci de l'avoir posée.

142voto

Rémi Rousselet Points 45139

Le problème vient de votre citation, qui est incorrecte.

Comme vous l'avez dit, les InheritedWidgets sont, comme les autres widgets, immuables. Par conséquent, ils ne mise à jour . Ils sont créés à nouveau.

Le truc c'est que : InheritedWidget est un simple widget qui ne fait rien d'autre que de contenir des données. . Il n'a pas de logique de mise à jour ou autre. Mais, comme tous les autres widgets, il est associé à une balise Element . Et devinez quoi ? Cette chose est mutable et flutter la réutilisera chaque fois que possible !

La citation corrigée serait :

InheritedWidget, lorsqu'il est référencé de cette manière, fera en sorte que le consommateur reconstruise lorsque InheritedWidget associé à un InheritedElement changements.

Il y a un excellent exposé sur la façon dont les widgets/éléments/renderbox sont assemblés. . Mais en bref, ils sont comme ceci (à gauche, le widget typique, au milieu, les "éléments", et à droite, les "boîtes de rendu") :

enter image description here

Le truc c'est que : Quand vous instanciez un nouveau widget, Flutter le compare à l'ancien. Réutilisez son "Element", qui pointe vers une RenderBox. Et muter les propriétés de la RenderBox.


D'accord, mais en quoi cela répond-il à ma question ?

Lorsque l'on instancie un InheritedWidget, puis que l'on appelle context.inheritedWidgetOfExactType (ou MyClass.of ce qui est fondamentalement la même chose) ; ce qui est implicite, c'est qu'elle écoutera le fichier Element associé à votre InheritedWidget . Et quand cela Element obtient un nouveau widget, il forcera le rafraîchissement de tous les widgets qui ont appelé la méthode précédente.

En bref, lorsque vous remplacez un InheritedWidget avec un tout nouveau ; flutter verra qu'il a changé. Et notifiera les widgets liés d'une modification potentielle.

Si vous avez tout compris, vous devriez déjà avoir deviné la solution :

Enveloppez votre InheritedWidget à l'intérieur d'un StatefulWidget qui créera un tout nouveau InheritedWidget dès que quelque chose a changé !

Le résultat final dans le code réel serait :

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;
  }
}

Mais la création d'un nouveau InheritedWidget ne reconstruirait-elle pas l'arbre entier ?

Non, ce ne sera pas nécessairement le cas. Comme votre nouveau InheritedWidget peut potentiellement avoir exactement le même enfant que précédemment. Et par exact, je veux dire la même instance. Les widgets qui ont la même instance qu'avant ne sont pas reconstruits.

Et dans la situation la plus favorable (avoir un widget hérité à la racine de votre application), le widget hérité est constant . Donc pas de reconstruction inutile.

2 votes

Mais la création d'un nouveau InheritedWidget ne reconstruirait-elle pas l'ensemble de l'arbre ? Pourquoi alors le besoin de Listeners ?

0 votes

Ok, vous pourriez modifier l'état des widgets Statefull en bas de l'arbre, je suppose. Mais il semble quand même assez fastidieux de propager les changements d'état de cette façon.

2 votes

Pour votre premier commentaire, j'ai ajouté une troisième partie à ma réponse. Quant au caractère fastidieux, je ne suis pas d'accord. Un extrait de code peut générer cela assez facilement. Et l'accès aux données est aussi simple que d'appeler MyInherited.of(context) .

28voto

maksimr Points 409

TL;DR

N'utilisez pas de calculs lourds à l'intérieur updateShouldNotify méthode et utilisation const au lieu de nouveau lors de la création d'un widget


Tout d'abord, nous devons comprendre ce que sont les objets Widget, Element et Render.

  1. Rendu sont ce qui est réellement rendu à l'écran. Ils sont mutable contiennent la logique de peinture et de mise en page. L'arbre de rendu est très similaire au Document Object Model (DOM) du web et vous pouvez considérer un objet de rendu comme un nœud DOM dans cet arbre.
  2. Widget - est une description de ce qui doit être rendu. Ils sont immuable et bon marché. Ainsi, si un widget répond à la question "Quoi ?" (approche déclarative), un objet de rendu répond à la question "Comment ?" (approche impérative). Une analogie avec le web est un "DOM virtuel".
  3. Élément/BuildContext - est un proxy entre Widget y Rendu des objets. Il contient des informations sur la position d'un widget dans l'arbre* et sur la manière de mettre à jour l'objet Render lorsqu'un widget correspondant est modifié.

Maintenant, nous sommes prêts à plonger dans InheritedWidget et la méthode de BuildContext hériter deWidgetOfExactType .

À titre d'exemple, je vous recommande de considérer cet exemple tiré de la documentation de Flutter sur InheritedWidget :

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  })  : assert(color != null),
        assert(child != null),
        super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor);
  }

  @override
  bool updateShouldNotify(FrogColor old) {
    return color != old.color;
  }
}

InheritedWidget - juste un widget qui implémente dans notre cas une méthode importante - le updateShouldNotify . updateShouldNotify - une fonction qui accepte un paramètre oldWidget et renvoie une valeur booléenne : true ou false.

Comme n'importe quel widget, InheritedWidget a un objet Élément correspondant. Il est InheritedElement . Appel à InheritedElement updateShouldNotify sur le widget chaque fois que nous construisons un nouveau widget (appel setState sur un ancêtre). Lorsque updateShouldNotify renvoie à vrai InheritedElement itère à travers Dépendances ( ?) et appeler la méthode didChangeDependencies sur elle.

Où InheritedElement obtient Dépendances ? Il convient ici d'examiner hériter deWidgetOfExactType méthode.

hériter deWidgetOfExactType - Cette méthode définie dans BuildContext et chaque Element implémente l'interface BuildContext (Element == BuildContext). Ainsi, chaque Element possède cette méthode.

Regardons le code de inheritFromWidgetOfExactType :

final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
  assert(ancestor is InheritedElement);
  return inheritFromElement(ancestor, aspect: aspect);
}

Ici, nous essayons de trouver un ancêtre dans _inheritedWidgets mappé par type. Si l'ancêtre est trouvé, nous appelons alors hériter d'un élément .

Le code pour hériter d'un élément :

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  1. Nous ajoutons l'ancêtre comme une dépendance de l'élément courant (_dependencies.add(ancestor))
  2. Nous ajoutons l'élément actuel aux dépendances de l'ancêtre (ancestor.updateDependencies(this, aspect))
  3. Nous retournons le widget de l'ancêtre en tant que résultat de hériter deWidgetOfExactType (return ancêtre.widget)

Nous savons donc maintenant où InheritedElement obtient ses dépendances.

Voyons maintenant didChangeDependencies méthode. Chaque élément possède cette méthode :

  void didChangeDependencies() {
    assert(_active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

Comme nous pouvons le voir, cette méthode marque simplement un élément comme étant sale et cet élément doit être reconstruit sur la prochaine image. Reconstruire signifie appeler la méthode construire sur l'élément widget correspondant.

Mais qu'en est-il de "Tout le sous-arbre se reconstruit lorsque je reconstruis InheritedWidget" ? Ici, nous devons nous rappeler que les widgets sont immuables et que si vous créez un nouveau widget, Flutter reconstruira la sous-arborescence. Comment pouvons-nous résoudre ce problème ?

  1. Cache les widgets à la main (manuellement)
  2. Utilisez const parce que la constance créer une seule instance de valeur/classe

2 votes

Excellente explication maksimr. Ce qui m'embrouille le plus, c'est que si l'ensemble du sous-arbre est reconstruit de toute façon lorsque le widget hérité est remplacé, quel est l'intérêt de updateShouldNotify() ?

0 votes

Donc ici, le widget hérité peut mettre à jour son écouteur si la valeur change et c'est exactement ce que fait le widget du fournisseur, alors quelle est la différence entre eux ? corrigez-moi si je me trompe.

0 votes

Où s'effectue le nettoyage, c'est-à-dire la suppression de toutes les dépendances des HashSet lorsque le widget est supprimé ?

4voto

kkurian Points 898

De la docs :

[BuildContext.inheritFromWidgetOfExactType] obtient le widget le plus proche le plus proche du type donné, qui doit être le type d'une sous-classe concrète de concrète InheritedWidget, et enregistre ce contexte de construction avec ce widget de sorte que, lorsque ce widget change (ou qu'un nouveau widget de ce type type est introduit, ou que le widget disparaît), ce contexte de construction est reconstruit afin de pouvoir obtenir de nouvelles valeurs de ce widget.

Elle est généralement appelée implicitement à partir des méthodes statiques of(), par exemple Thème.of.

Comme l'a noté le PO, un InheritedWidget ne change pas... mais elle peut être remplacée par une nouvelle instance au même endroit dans l'arbre des widgets. Lorsque cela se produit, il est possible que les widgets enregistrés doivent être reconstruits. Le site InheritedWidget.updateShouldNotify fait cette détermination. (Voir : docs )

Comment remplacer une instance ? Un site InheritedWidget peut être contenue par une instance StatefulWidget qui peut remplacer une ancienne instance par une nouvelle.

-5voto

Sunil Points 420

InheritedWidget gère les données centralisées de l'application et les passe à l'enfant, Comme nous pouvons stocker ici le nombre de panier comme expliqué aquí :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X