138 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 chance de propager les données vers le bas le Widget de l'arbre. Dans l'extrême si vous la mettre en RootWidget il sera accessible à partir de tous les Widgets dans l'arbre sur toutes les Routes, ce qui est bien parce que de toute façon je dois faire mon ViewModel/Modèle accessible pour mes trucs sans avoir à recourir à des variables globales ou des Singletons.

MAIS InheritedWidget est immuable, alors comment puis-je mettre à jour? Et plus important, comment sont mes Stateful Widgets déclenchée à reconstruire leurs sous-arbres?

Malheureusement, la documentation est ici très floue et après en avoir discuté avec beaucoup de 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 en bas de l'arbre. Ce que je trouve déroutant, à partir de l'API docs:

"Héritée des widgets référencées dans cette voie, sera la cause de la la consommation de reconstruire lorsque les hérité widget lui-même change d'état."

Quand j'ai lu cela, j'ai pensé:

Je pourrais trucs certaines données dans le InheritedWidget et de les transformer plus tard. Lorsque que la mutation se produit, il faudra recompiler tous les Widgets référence de mon InheritedWidget Ce que j'ai trouvé:

Pour la mutation de l'État d'un InheritedWidget, vous avez besoin d'envelopper dans un StatefulWidget Vous alors effectivement la mutation de l'état de l' StatefulWidget et de transmettre ces données vers le InheritedWidget, qui les mains des données à tous ses enfants. Toutefois, dans ce cas, il semble de reconstruire l'ensemble de l'arborescence en dessous de la StatefulWidget, pas juste les Widgets qui font référence à la InheritedWidget. Est-ce exact? Ou cela est-il en quelque sorte de savoir comment faire pour ignorer les Widgets qui font référence à la InheritedWidget si updateShouldNotify renvoie false?

126voto

Rémi Rousselet Points 45139

Le problème vient de votre devis, ce qui est incorrect.

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

Le truc, c'est: InheritedWidget est juste un widget simple qui ne fait rien mais la détention de données. Ce n'est pas une logique de mise à jour ou que ce soit. Mais, comme n'importe quel autre widget, il est associé à un Element. Et devinez quoi? Cette chose est mutable et flutter va réutiliser autant que possible!

Le corrigé citation serait :

InheritedWidget référencées dans cette voie, va amener le consommateur à se reconstruire quand InheritedWidget associé à un InheritedElement changements.

Il y a un grand discours sur la façon widgets/elements/renderbox sont branché ensemble. Mais en bref, ils sont (de gauche est typique de votre widget, le milieu est "d'éléments", et la droite sont des "render cases") :

enter image description here

Le truc, c'est: Lorsque vous instanciez un nouveau widget; flutter va le comparer à l'ancien. Réutiliser c'est "l'Élément", qui pointe vers une RenderBox. Et muter le RenderBox propriétés.


Okey, mais comment est-ce répondre à ma question ?

Lors de l'instanciation d'un InheritedWidget, puis en appelant context.inheritedWidgetOfExactType (ou MyClass.of qui est fondamentalement la même chose) ; ce qui est implicite, c'est qu'il sera à l'écoute de l' Element associée à votre InheritedWidget. Et à chaque fois qu' Element obtient un nouveau widget, il va forcer l'actualisation de tous les widgets qui a appelé la méthode précédente.

En bref, si vous remplacez un existant, InheritedWidget avec une nouvelle marque; flutter va voir qu'il a changé. Et en informera la limite des widgets d'une éventuelle modification.

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

Enveloppez votre InheritedWidget à l'intérieur d'un StatefulWidget qui permettra de créer une nouvelle marque d' InheritedWidget chaque fois que quelque chose a changé!

Le résultat final dans le code 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);

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

Mais ne serait pas la création d'une nouvelle InheritedWidget reconstruire l'ensemble de l'arbre ?

Non, il ne sera pas nécessairement. En tant que votre nouveau InheritedWidget peut avoir exactement le même enfant comme avant. Et par exact, je veux dire la même instance. Les Widgets qui ont la même instance qu'ils avaient avant de ne pas reconstruire.

Et dans la plupart des situation (le fait d'Avoir un inheritedWidget à la racine de votre application), l'héritage widget est constante. Donc pas inutile de les reconstruire.

27voto

maksimr Points 409

TL;DR

N'utilisez pas lourd calcul à l'intérieur de updateShouldNotify méthode et l'utilisation de const à la place de nouveau lors de la création d'un widget


Tout d'abord, nous devons comprendre ce qu'est un Widget, Élément et de Rendre des objets.

  1. Rendre les objets sont ce qui est réellement affiché sur l'écran. Ils sont mutables, contiennent de la peinture et de la logique de présentation. Le Rendu est un arbre très semblable au Modèle d'Objet de Document(DOM) dans le web et vous pouvez regarder un rendu de l'objet comme un nœud DOM dans cet arbre
  2. Widget - est une description de ce qui doit être rendu. Ils sont immuables et pas cher. Donc, si un Widget répond à la question "Quoi?"(Approche déclarative) puis un Rendu objet de répondre à la question "Comment?"(Impératif approche). Une analogie à partir du web est un "Virtual DOM".
  3. Élément/BuildContext - est un proxy entre le Widget et le Rendu des objets. Il contient des informations sur la position d'un composant dans l'arbre* et comment mettre à jour le Rendu de l'objet lorsqu'un widget est changé.

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

Un exemple, je recommande que nous considérons cet exemple de Flottement de la documentation 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 met en oeuvre, dans notre cas, une méthode importante - 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 correspond à un Élément de l'objet. Il est InheritedElement. InheritedElement appel updateShouldNotify sur le widget à chaque fois que nous à en construire un nouveau widget(appel setState sur un ancêtre). Lorsque updateShouldNotify retourne vrai InheritedElement parcourt les dépendances(?) et d'appeler la méthode didChangeDependencies sur elle.

Où InheritedElement obtient des dépendances? Ici, nous devrions regarder inheritFromWidgetOfExactType méthode.

inheritFromWidgetOfExactType - Cette méthode définie dans BuildContext et chaque Élément met en œuvre BuildContext interface (Élément == BuildContext). Ainsi, chaque Élément a de cette méthode.

Permet de regarder 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 cartographiés par type. Si l'ancêtre est trouvé, on l'appellera alors inheritFromElement.

Le code pour inheritFromElement:

  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 ancêtre comme une dépendance de l'élément courant (_dependencies.ajouter(ancêtre))
  2. Nous ajoutons l'élément courant de l'ancêtre de dépendances (ancêtre.updateDependencies(ce, aspect))
  3. Nous revenons ancêtre du widget comme résultat de inheritFromWidgetOfExactType (retour ancêtre.widget)

Alors maintenant, nous savons où InheritedElement obtient ses dépendances.

Regardons maintenant didChangeDependenciesméthode. Chaque Élément a 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 constitue un élément comme sale et cet élément doit être reconstruit sur l'image suivante. Reconstruire les moyens d'appel de méthode à construire sur le widget correspondant de l'élément.

Mais quid de l'Ensemble de la sous-arbre reconstruit quand j'ai reconstruire InheritedWidget?". Ici, nous devons nous rappeler que les Widgets sont immuables et si vous créer un nouveau widget Flutter va reconstruire le sous-arbre. Comment pouvons-nous résoudre ce problème?

  1. Cache widgets par les mains(manuellement)
  2. Utiliser const car const créer une seule instance de valeur/classe

4voto

kkurian Points 898

À partir de la docs:

[BuildContext.inheritFromWidgetOfExactType] obtient le plus proche widget d'un type donné, qui doit être le type d'un béton InheritedWidget sous-classe, et les registres de cette construction d'un contexte avec qui widget de telle manière que lorsque que le widget de changements (ou un nouveau widget de de type est introduite, ou le widget s'en va), ce build contexte est reconstruit afin qu'il puisse obtenir de nouvelles valeurs à partir de ce widget.

Ceci est généralement appelé implicitement à partir de() les méthodes statiques, par exemple Thème.de.

Comme l'OP mentionné, InheritedWidget instance ne change pas... mais il peut être remplacé par une nouvelle instance au même endroit dans le widget de l'arbre. Lorsque cela arrive, il est possible que le régime enregistré d'widgets besoin d'être reconstruit. L' InheritedWidget.updateShouldNotify méthode rend cette détermination. (Voir: docs)

Alors, comment pourrait-il être remplacé? Un InheritedWidget instance peut être contenue par un StatefulWidget, qui peut remplacer une ancienne version avec une nouvelle instance.

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