Was ist der Unterschied zwischen Funktionen und Klassen, um wiederverwendbare Widgets zu erstellen?


124

Ich habe festgestellt, dass es möglich ist, Widgets mit einfachen Funktionen zu erstellen, anstatt StatelessWidget zu unterordnen . Ein Beispiel wäre dies:

Widget function({ String title, VoidCallback callback }) {
  return GestureDetector(
    onTap: callback,
    child: // some widget
  );
}

Dies ist interessant, da es weit weniger Code erfordert als eine vollständige Klasse. Beispiel:

class SomeWidget extends StatelessWidget {
  final VoidCallback callback;
  final String title;

  const SomeWidget({Key key, this.callback, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
      return GestureDetector(
        onTap: callback,
        child: // some widget
      );
  }
}

Ich habe mich also gefragt: Gibt es neben der Syntax einen Unterschied zwischen Funktionen und Klassen, um Widgets zu erstellen? Und ist es eine gute Praxis, Funktionen zu verwenden?


Ich fand diesen Thread sehr nützlich für mein Verständnis des Problems. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Antworten:


171

TL; DR: Verwenden Sie lieber Klassen als Funktionen, um einen wiederverwendbaren Widget-Baum zu erstellen.


EDIT : Um einige Missverständnisse auszugleichen: Hier geht es nicht um Funktionen, die Probleme verursachen, sondern um Klassen, die einige lösen.

Flutter hätte StatelessWidget nicht, wenn eine Funktion dasselbe tun könnte.

Ebenso richtet es sich hauptsächlich an öffentliche Widgets, die zur Wiederverwendung bestimmt sind. Es ist weniger wichtig, dass private Funktionen nur einmal verwendet werden - obwohl es immer noch gut ist, sich dieses Verhaltens bewusst zu sein.


Es gibt einen wichtigen Unterschied zwischen der Verwendung von Funktionen anstelle von Klassen, dh: Das Framework kennt keine Funktionen, kann jedoch Klassen sehen.

Betrachten Sie die folgende "Widget" -Funktion:

Widget functionWidget({ Widget child}) {
  return Container(child: child);
}

auf diese Weise verwendet:

functionWidget(
  child: functionWidget(),
);

Und es ist Klassenäquivalent:

class ClassWidget extends StatelessWidget {
  final Widget child;

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

  @override
  Widget build(BuildContext context) {
    return Container(
      child: child,
    );
  }
}

so verwendet:

new ClassWidget(
  child: new ClassWidget(),
);

Auf dem Papier scheinen beide genau dasselbe zu tun: Erstellen Sie 2 Container, wobei eines in das andere verschachtelt ist. Aber die Realität sieht etwas anders aus.

Bei Funktionen sieht der generierte Widget-Baum folgendermaßen aus:

Container
  Container

Während des Unterrichts lautet der Widget-Baum:

ClassWidget
  Container
    ClassWidget
      Container

Dies ist wichtig, da sich dadurch das Verhalten des Frameworks beim Aktualisieren eines Widgets ändert.

Warum das wichtig ist

Durch die Verwendung von Funktionen zum Aufteilen Ihres Widget-Baums in mehrere Widgets setzen Sie sich Fehlern aus und verpassen einige Leistungsoptimierungen.

Es gibt keine Garantie , dass Sie werden Fehler haben von Funktionen, sondern von Klassen verwenden, werden Sie garantiert nicht , diese Fragen stellen.

Hier sind einige interaktive Beispiele auf Dartpad, die Sie selbst ausführen können, um die Probleme besser zu verstehen:

Fazit

Hier ist eine kuratierte Liste der Unterschiede zwischen der Verwendung von Funktionen und Klassen:

  1. Klassen:
  • Leistungsoptimierung zulassen (const Konstruktor, detailliertere Neuerstellung)
  • Stellen Sie sicher, dass durch das Umschalten zwischen zwei verschiedenen Layouts die Ressourcen korrekt entsorgt werden (Funktionen können einen früheren Status wiederverwenden).
  • stellt sicher, dass das Hot-Reload ordnungsgemäß funktioniert (die Verwendung von Funktionen kann das Hot-Reload für showDialogs& ähnliches unterbrechen )
  • sind in den Widget-Inspektor integriert.
    • Wir sehen ClassWidgetin dem Widget-Baum, der vom devtool angezeigt wird, was hilft, zu verstehen, was auf dem Bildschirm angezeigt wird
    • Wir können debugFillProperties überschreiben , um die an ein Widget übergebenen Parameter zu drucken
  • Bessere Fehlermeldungen
    Wenn eine Ausnahme auftritt (wie ProviderNotFound), gibt Ihnen das Framework den Namen des aktuell erstellten Widgets. Wenn Sie Ihren Widget-Baum nur in Funktionen + aufgeteilt haben Builder, haben Ihre Fehler keinen hilfreichen Namen
  • kann Schlüssel definieren
  • kann die Kontext-API verwenden
  1. Funktionen:
  • weniger Code haben (was mit der Codegenerierung funktional_widget gelöst werden kann )

Insgesamt wird es aus diesen Gründen als schlechte Praxis angesehen, Funktionen über Klassen für die Wiederverwendung von Widgets zu verwenden.
Sie können , aber es kann Sie in der Zukunft beißen.


Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew

10

Ich habe in den letzten 2 Tagen zu diesem Thema recherchiert. Ich bin zu folgendem Schluss gekommen: Es ist OK, Teile der App in Funktionen zu zerlegen. Es ist nur ideal, dass diese Funktionen a zurückgeben StatelessWidget, damit Optimierungen vorgenommen werden können, z. B. das Erstellen des StatelessWidget const, damit es nicht neu erstellt wird, wenn es nicht muss. Zum Beispiel ist dieser Code vollkommen gültig:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            const MyWidgetClass(key: const Key('const')),
            MyWidgetClass(key: Key('non-const')),
            _buildSomeWidgets(_counter),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _buildSomeWidgets(int val) {
    print('${DateTime.now()} Rebuild _buildSomeWidgets');
    return const MyWidgetClass(key: Key('function'));

    // This is bad, because it would rebuild this every time
    // return Container(
    //   child: Text("hi"),
    // );
  }
}

class MyWidgetClass extends StatelessWidget {
  const MyWidgetClass({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('${DateTime.now()} Rebuild MyWidgetClass $key');

    return Container(
      child: Text("hi"),
    );
  }
}

Die Verwendung der Funktion dort ist vollkommen in Ordnung, da sie a zurückgibt const StatelessWidget. Bitte korrigieren Sie mich, wenn ich falsch liege.


Kann jemand erklären, warum das, was ich gesagt habe, falsch ist? Ich meine, ich nehme an, es ist falsch angesichts der Abstimmungen.
Sergiu Iacob

Ich stimme dir tatsächlich zu. Ich hatte vor, eine viel detailliertere Aufschlüsselung der Unterschiede zu schreiben, bin aber noch nicht dazu gekommen. Fühlen Sie sich frei, Ihre Argumentation zu konkretisieren, da ich denke, dass es wichtig ist, die Vor- und Nachteile von Widgets gegen Methoden zu verstehen.
TheIT

@SergiuIacob Können wir constfür jeden Fall vor der zustandslosen Klasse verwenden? Oder müssen es bestimmte Fälle sein? Wenn ja, was sind sie?
Aytunch

1
@aytunch Ich glaube nicht, dass Sie constüberall verwenden können. Wenn Sie beispielsweise eine StatelessWidgetKlasse haben, die einen TextWert zurückgibt , der den Wert einer Variablen enthält, und diese Variable sich irgendwo ändert, StatelessWidgetsollte Ihre neu erstellt werden, damit sie diesen anderen Wert anzeigen kann, daher kann dies nicht der Fall sein const. Ich denke, der sichere Weg, es auszudrücken, ist folgender: Wo immer Sie können, verwenden Sie es const, wenn es sicher ist.
Sergiu Iacob

3
Ich habe überlegt, ob ich diese Frage selbst beantworten soll. Die akzeptierte Antwort ist eindeutig falsch, aber Rémi hat viel getan, um der Flattern-Community zu helfen, sodass die Leute seine Antworten wahrscheinlich nicht so genau hinterfragen wie die anderer. Das könnte aus allen Gegenstimmen hervorgehen. Die Leute wollen nur ihre "einzige Quelle der Wahrheit". :-)
DarkNeuron

4

Es gab einen großen Unterschied zwischen den Funktionen und der Klasse.


Lassen Sie es uns von Grund auf erklären. « (Nur über Imperativ)

  • Wir alle wissen, dass die Programmierhistorie mit einfachen Grundbefehlen begann (zB: Assembly).

  • Die nächste strukturierte Programmierung wurde mit Flusssteuerungen geliefert (z. B. if, switch, while, for usw.). Dieses Paradigma gibt Programmierern die Möglichkeit, den Programmfluss effektiv zu steuern und die Anzahl der Codezeilen durch Schleifen zu minimieren.

  • Als nächstes kam die prozedurale Programmierung, die Anweisungen in Prozeduren (Funktionen) gruppiert. Dies gab zwei Hauptvorteile für Programmierer.

    1.Gruppenanweisungen (Operationen) in separate Blöcke.

    2.Kann diese Blöcke wiederverwenden. (Funktionen)

Vor allem aber Paradigmen nicht geben , eine Lösung für Anwendungen verwalten. Die prozedurale Programmierung kann auch nur für kleine Anwendungen verwendet werden. Dies kann nicht für die Entwicklung großer Webanwendungen (z. B. Banking, Google, Youtube, Facebook, Stackoverflow usw.) verwendet werden. Es können keine Frameworks wie Android SDK, Flutter SDK und vieles mehr erstellt werden.

Daher recherchieren die Ingenieure viel mehr, um Programme ordnungsgemäß zu verwalten.

  • Schließlich bietet die objektorientierte Programmierung die gesamte Lösung für die Verwaltung von Anwendungen in jeder Größenordnung (von der Hallo-Welt bis zu Billionen von Menschen, die die Systemerstellung verwenden, z. B. Google, Amazon und heute 90% der Anwendungen).

  • In oop werden alle Anwendungen um Objekte herum erstellt. Dies bedeutet, dass die Anwendung eine Sammlung dieser Objekte ist.

Objekte sind also das Grundgebäude für jede Anwendung.

Gruppendaten und Funktionen der Klasse (Objekt zur Laufzeit) in Bezug auf diese Variablen (Daten). Objekt besteht also aus Daten und den damit verbundenen Operationen.

[Hier werde ich nicht über oop erklären]


»Okay, jetzt kommen wir zum Flattern.«

-Dart unterstützt sowohl prozedurale als auch oop-Funktionen. Das Flutter-Framework wird jedoch vollständig mithilfe von Klassen (oop) erstellt. (Weil ein großes verwaltbares Framework nicht mithilfe von Prozeduren erstellt werden kann.)

Hier werde ich eine Liste der Gründe erstellen, warum sie Klassen anstelle von Funktionen zum Erstellen von Widgets verwenden


1 - In den meisten Fällen ruft die Build-Methode (untergeordnetes Widget) die Anzahl der synchronen und asynchronen Funktionen auf.

Ex:

  • Netzwerkbild herunterladen
  • Eingaben vom Benutzer usw. erhalten

Daher muss die Build-Methode in einem separaten Klassen-Widget gespeichert werden (da alle anderen Methoden, die von der build () -Methode aufgerufen werden, in einer Klasse gespeichert werden können).


2 - Mit der Widget-Klasse können Sie die Nummer einer anderen Klasse erstellen, ohne immer wieder denselben Code zu schreiben (** Verwendung der Vererbung ** (erweitert)).

Mit Vererbung (Erweitern) und Polymorphismus (Überschreiben) können Sie auch eine eigene benutzerdefinierte Klasse erstellen. (Unten im Beispiel: Dort werde ich die Animation anpassen (überschreiben), indem ich MaterialPageRoute erweitere (weil ich den Standardübergang anpassen möchte) .👇

class MyCustomRoute<T> extends MaterialPageRoute<T> {
  MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
      : super(builder: builder, settings: settings);

  @override                                      //Customize transition
  Widget buildTransitions(BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    if (settings.isInitialRoute)
      return child;
    // Fades between routes. (If you don't want any animation, 
    // just return child.)
    return new FadeTransition(opacity: animation, child: child);
  }
}

3 - Funktionen können keine Bedingungen für ihre Parameter hinzufügen, aber mit dem Konstruktor des Klassen-Widgets können Sie dies tun.

Unten Codebeispiel👇 (diese Funktion wird häufig von Framework-Widgets verwendet)

const Scaffold({
    Key key,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  }) : assert(primary != null),
       assert(extendBody != null),
       assert(extendBodyBehindAppBar != null),
       assert(drawerDragStartBehavior != null),
       super(key: key);

4 - Funktionen können const nicht verwenden und das Klassen-Widget kann die const für ihre Konstruktoren verwenden. (die die Leistung des Hauptthreads beeinflussen)


5 - Sie können eine beliebige Anzahl unabhängiger Widgets mit derselben Klasse (Instanzen einer Klasse / von Objekten) erstellen. Die Funktion kann jedoch keine unabhängigen Widgets (Instanz) erstellen, sondern wiederverwenden.

[Jede Instanz hat ihre eigene Instanzvariable und diese ist völlig unabhängig von anderen Widgets (Objekt). Die lokale Variable der Funktion hängt jedoch von jedem Funktionsaufruf ab * (dh wenn Sie einen Wert einer lokalen Variablen ändern, wirkt sich dies auf alle anderen Teile von aus die Anwendung, die diese Funktion verwendet)]


Es gab viele Vorteile in der Klasse gegenüber Funktionen. (Oben sind nur einige Anwendungsfälle aufgeführt.)


🤯 Mein letzter Gedanke

Verwenden Sie Funktionen also nicht als Baustein Ihrer Anwendung, sondern nur für Operationen. Andernfalls verursacht es viele nicht handhabbare Probleme, wenn Ihre Anwendung skalierbar wird .

  • Verwenden Sie Funktionen, um einen kleinen Teil der Aufgabe zu erledigen
  • Klasse als Baustein einer Anwendung verwenden (Anwendung verwalten)

📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍 📍📍📍📍📍📍📍

Sie können die Qualität des Programms nicht anhand der Anzahl der von ihm verwendeten Aussagen (oder Zeilen) messen

📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍 📍📍📍📍📍📍📍

Danke fürs Lesen


Willkommen bei Stackoverflow! Ich bin mir nicht sicher, was Sie mit Ihrer Antwort ausdrücken wollen. Sie können eine Funktion zum Erstellen von Widgets verwenden. shrinkHelper() { return const SizedBox.shrink(); }Dies entspricht der Verwendung von const SizedBox.shrink()Inline in Ihrem Widget-Baum. Mithilfe von Hilfsfunktionen können Sie die Anzahl der Verschachtelungen an einer Stelle begrenzen.
DarkNeuron

@ DarkNeuron Danke fürs Teilen. Ich werde versuchen, Hilfsfunktionen zu verwenden.
TDM

2

Stellen Sie beim Aufrufen des Flutter-Widgets sicher, dass Sie das Schlüsselwort const verwenden. Beispielsweiseconst MyListWidget();


9
Darf ich wissen, wie dies die OP-Frage beantwortet?
CopsOnRoad

2
Sieh aus, als hätte ich auf den falschen Abschnitt geantwortet. Ich habe versucht, Daniels Frage zu beantworten, dass die überarbeitete Methode zum Erstellen eines zustandslosen Widgets immer noch aufgerufen wird. Durch Hinzufügen des constSchlüsselworts beim Aufrufen des überarbeiteten zustandslosen Widgets sollte es nur einmal aufgerufen werden.
user4761410

1
OK. Verstanden. Die Leute können diese Antwort ablehnen, da sie nichts mit der OP-Frage zu tun hat. Also solltest du es löschen. Wie auch immer, Sie haben die Wahl.
CopsOnRoad
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.