InheritedWidget in Flutter

Die Übersetzung des Artikels wurde für Studenten des Flutter Mobile Developer- Kurses vorbereitet .










Die Wurzeln von Flutter-Widget-Bäumen können sehr tief gehen ...







Sehr tief.



Die Komponentennatur von Flutter-Widgets ermöglicht sehr elegante, modulare und flexible Anwendungsdesigns. Dies kann jedoch auch zu viel Code für die Übergabe des Kontexts führen. Überprüfen Sie, was passiert, wenn wir die accountId und die scopeId von der Seite an das Widget in zwei Ebenen weiter unten übergeben möchten:



class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyWidget(accountId, scopeId);
  }
}

class MyWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    // -   
    new MyOtherWidget(accountId, scopeId);
    ...
  }
}

class MyOtherWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyOtherWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    //  
    ...


Wenn dieses Kontrollkästchen nicht aktiviert ist, kann sich dieses Muster sehr leicht über die gesamte Codebasis kriechen. Auf diese Weise haben wir über 30 Widgets persönlich parametrisiert. Fast die Hälfte der Arbeitszeit erhielt das Widget Parameter, um sie weiterzuleiten, wie im MyWidgetobigen Beispiel.



Der Status von MyWidget ist unabhängig von Parametern und wird dennoch jedes Mal neu erstellt, wenn sich die Parameter ändern!


Natürlich muss es einen besseren Weg geben ...



Einführung in InheritedWidget .



Kurz gesagt, es ist eine spezielle Art von Widget, das den Kontext an der Wurzel eines Teilbaums definiert. Dieser Kontext kann effizient für jedes Widget in diesem Teilbaum bereitgestellt werden. Das Zugriffsmuster sollte einem Flutter-Entwickler bekannt vorkommen:



final myInheritedWidget = MyInheritedWidget.of(context);


Dieser Kontext ist nur eine Dart-Klasse. Somit kann es alles enthalten, was Sie dort stopfen möchten. Viele der häufig verwendeten Flutter-Kontexte wie Styleoder MediaQuerysind nichts anderes als InheritedWidgets, die auf der Ebene leben MaterialApp.



Wenn wir das obige Beispiel mit InheritedWidget ergänzen, erhalten wir Folgendes:



class MyInheritedWidget extends InheritedWidget {
  final int accountId;
  final int scopeId;

  MyInheritedWidget(accountId, scopeId, child): super(child);
  
  @override
  bool updateShouldNotify(MyInheritedWidget old) =>
    accountId != old.accountId || scopeId != old.scopeId;
}

class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      accountId,
      scopeId,
      const MyWidget(),
     );
  }
}

class MyWidget extends StatelessWidget {

  const MyWidget();
  
  Widget build(BuildContext context) {
    // -   
    const MyOtherWidget();
    ...
  }
}

class MyOtherWidget extends StatelessWidget {

  const MyOtherWidget();
  
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    print(myInheritedWidget.scopeId);
    print(myInheritedWidget.accountId);
    ...


Es ist wichtig zu beachten:



  • Konstruktoren constmachen diese Widgets jetzt zwischenspeicherbar . das erhöht die Produktivität.
  • Wenn die Parameter aktualisiert werden, wird ein neuer erstellt MyInheritedWidget. Im Gegensatz zum ersten Beispiel wird der Teilbaum jedoch nicht neu erstellt. Stattdessen verwaltet Flutter eine interne Registrierung, die die Widgets verfolgt, die darauf zugegriffen haben InheritedWidget, und nur diejenigen neu erstellt, die diesen Kontext verwenden. In diesem Beispiel ist es MyOtherWidget.
  • Wenn der Baum aus anderen Gründen als Parameteränderungen, wie z. B. Orientierungsänderungen, neu erstellt wird, kann Ihr Code dennoch einen neuen erstellen InheritedWidget. Da die Parameter jedoch gleich bleiben, werden die Widgets im Teilbaum nicht benachrichtigt. Dies ist der Zweck der updateShouldNotifyvon Ihnen implementierten Funktion InheritedWidget.


Lassen Sie uns abschließend über bewährte Verfahren sprechen.



InheritedWidget sollte klein sein



Das Überladen mit viel Kontext führt zum Verlust der oben genannten zweiten und dritten Vorteile, da Flutter nicht bestimmen kann, welcher Teil des Kontexts aktualisiert wird und welcher Teil von Widgets verwendet wird. Stattdessen:



class MyAppContext {
  int teamId;
  String teamName;
  
  int studentId;
  String studentName;
  
  int classId;
  ...
}


Am liebsten:



class TeamContext {
  int teamId;
  String teamName;
}

class StudentContext {
  int studentId;
  String studentName;
}
 
class ClassContext {
  int classId;
  ...
}


Verwenden Sie const, um Ihre Widgets zu erstellen



Ohne const gibt es keine selektive Neuerstellung des Teilbaums. Flutter erstellt eine neue Instanz jedes Widgets im Teilbaum und ruft sie auf build(), wodurch wertvolle Zyklen verschwendet werden, insbesondere wenn Ihre Erstellungsmethoden schwer genug sind.



Behalten Sie den Umfang Ihres InheritedWidget-s im Auge



InheritedWidget-y werden an der Wurzel des Widget-Baums platziert. Dies bestimmt in der Tat ihren Umfang. In unserem Team stellten wir fest, dass es übertrieben war , den Kontext an einer beliebigen Stelle im Widget-Baum deklarieren zu können . Wir haben beschlossen, unsere kontextbezogenen Widgets so einzuschränken, dass sie nur Scaffold(oder ihre Derivate) als Kinder akzeptieren . Auf diese Weise stellen wir sicher, dass sich der detaillierteste Kontext auf Seitenebene befindet, und erhalten zwei Bereiche:



  • Widgets auf Anwendungsebene wie z MediaQuery. Sie stehen jedem Widget auf jeder Seite Ihrer Anwendung zur Verfügung, da sie sich im Stammverzeichnis des Widget-Baums Ihrer Anwendung befinden.
  • Widgets auf Seitenebene wie MyInheritedWidgetim obigen Beispiel.


Sie sollten das eine oder das andere auswählen, je nachdem, wo der Kontext gilt.



Widgets auf Seitenebene können keine Routengrenze überschreiten



Es scheint offensichtlich. Dies hat jedoch schwerwiegende Auswirkungen, da die meisten Anwendungen mehr als eine Navigationsebene haben. So könnte Ihre Anwendung aussehen:



> School App [App Context]
  > Student [Student Context]
    > Grades
    > Bio
  > Teacher [Teacher Context]
    > Courses
    > Bio


Das sieht Flutter:



> School App [App Context]
  > Student [Student Context]
  > Student Grades
  > Student Bio
  > Teacher [Teacher Context]
  > Teacher Courses
  > Teacher Bio


Aus Flatters Sicht gibt es keine Navigationshierarchie. Jede Seite (oder jedes Gerüst) ist ein Baum von Widgets, die einem Anwendungs-Widget zugeordnet sind. Wenn Sie Navigator.pushdiese Seiten zum Anzeigen verwenden, erben sie daher nicht das Widget, das den übergeordneten Kontext enthält. Im obigen Beispiel müssen Sie den Kontext explizit Studentvon der Student-Seite an die Student Bio-Seite übergeben.



Es gibt zwar verschiedene Möglichkeiten, den Kontext zu übergeben, ich empfehle jedoch, Routen auf altmodische Weise zu parametrisieren (z. B. URL-Codierung, wenn Sie benannte Routen verwenden). Außerdem wird sichergestellt, dass Seiten ausschließlich auf der Grundlage der Route erstellt werden können, ohne dass der Kontext der übergeordneten Seite verwendet werden muss.



Viel Spaß beim Codieren!



Seien Sie pünktlich zum Kurs!



All Articles