Jaka jest różnica między funkcjami a klasami do tworzenia widżetów wielokrotnego użytku?

125

Zdałem sobie sprawę, że możliwe jest tworzenie widgetów przy użyciu prostych funkcji zamiast tworzenia podklas StatelessWidget . Przykładem może być:

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

Jest to interesujące, ponieważ wymaga znacznie mniej kodu niż pełnowymiarowa klasa. Przykład:

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

Więc zastanawiałem się: czy jest jakaś różnica poza składnią między funkcjami i klasami do tworzenia widżetów? Czy korzystanie z funkcji jest dobrą praktyką?

Rémi Rousselet
źródło
Ten wątek okazał się bardzo przydatny dla zrozumienia problemu. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Odpowiedzi:

173

TL; DR: Preferuj używanie klas zamiast funkcji, aby utworzyć drzewo widżetów wielokrotnego użytku .


EDYCJA : Żeby nadrobić pewne nieporozumienia: nie chodzi o funkcje powodujące problemy, ale zajęcia rozwiązujące niektóre.

Flutter nie miałby StatelessWidget, gdyby funkcja mogła zrobić to samo.

Podobnie jest skierowany głównie do widżetów publicznych, stworzonych do ponownego wykorzystania. Nie ma to większego znaczenia dla funkcji prywatnych, które mają być używane tylko raz - chociaż świadomość tego zachowania jest nadal dobra.


Istnieje ważna różnica między używaniem funkcji zamiast klas, to znaczy: Framework nie zna funkcji, ale widzi klasy.

Rozważ następującą funkcję „widżetu”:

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

używany w ten sposób:

functionWidget(
  child: functionWidget(),
);

I jest to odpowiednik klasy:

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

używane w ten sposób:

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

Na papierze oba wydają się robić dokładnie to samo: Utwórz 2 Container, z jednym zagnieżdżonym w drugim. Ale rzeczywistość jest nieco inna.

W przypadku funkcji wygenerowane drzewo widżetów wygląda następująco:

Container
  Container

W przypadku klas drzewo widżetów to:

ClassWidget
  Container
    ClassWidget
      Container

Jest to ważne, ponieważ zmienia sposób, w jaki struktura zachowuje się podczas aktualizowania widgetu.

Dlaczego to ma znaczenie

Używając funkcji do podzielenia drzewa widżetów na wiele widżetów, narażasz się na błędy i pomijasz niektóre optymalizacje wydajności.

Nie ma gwarancji, że będziesz mieć błędy przy użyciu funkcji, ale używając klas, masz gwarancję, że nie napotkasz tych problemów.

Oto kilka interaktywnych przykładów na Dartpad, które możesz uruchomić samodzielnie, aby lepiej zrozumieć problemy:

Wniosek

Oto lista różnic między używaniem funkcji i klas:

  1. Zajęcia:
  • pozwalają na optymalizację wydajności (konstruktor const, bardziej szczegółowa przebudowa)
  • upewnij się, że przełączanie się między dwoma różnymi układami prawidłowo usuwa zasoby (funkcje mogą ponownie używać poprzedniego stanu)
  • zapewnia, że ​​funkcja Hot-Reload działa poprawnie (używanie funkcji może spowodować przerwanie funkcji Hot-Reload dla itp. showDialogs)
  • są zintegrowane z inspektorem widżetów.
    • Widzimy ClassWidgetw drzewie widżetów pokazanym przez devtool, które pomaga zrozumieć, co jest na ekranie
    • Możemy przesłonić debugFillProperties, aby wydrukować parametry przekazane do widgetu
  • lepsze komunikaty o błędach
    Jeśli wystąpi wyjątek (np. ProviderNotFound), framework poda nazwę aktualnie budowanego widżetu. Jeśli podzieliłeś drzewo widżetów tylko na functions + Builder, błędy nie będą miały użytecznej nazwy
  • może definiować klucze
  • może używać kontekstowego interfejsu API
  1. Funkcje:

Ogólnie rzecz biorąc, używanie funkcji zamiast klas w celu ponownego użycia widżetów jest uważane za złą praktykę z tych powodów.
Państwo może , ale może cię ugryźć w przyszłości.

Rémi Rousselet
źródło
Komentarze nie służą do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Samuel Liew
10

Badałem ten problem przez ostatnie 2 dni. Doszedłem do następującego wniosku: w porządku jest rozbić elementy aplikacji na funkcje. Idealnie StatelessWidgetbyłoby, gdyby te funkcje zwracały a , więc można wprowadzić optymalizacje, takie jak tworzenie StatelessWidget const, więc nie odbudowuje się, jeśli nie musi. Na przykład ten fragment kodu jest całkowicie poprawny:

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"),
    );
  }
}

Użycie tam funkcji jest w porządku, ponieważ zwraca a const StatelessWidget. Proszę popraw mnie jeżeli się mylę.

Sergiu Iacob
źródło
Czy ktoś może wyjaśnić, dlaczego to, co powiedziałem, jest złe? To znaczy, przypuszczam, że to źle, biorąc pod uwagę głosy przeciw.
Sergiu Iacob
Właściwie się z tobą zgadzam. Zamierzałem napisać znacznie bardziej szczegółowe zestawienie różnic, ale nie zabrałem się za to. Zapraszam do rozwinięcia argumentu, ponieważ uważam, że ważne jest, aby zrozumieć zalety i wady metod widżetów i metod.
TheIT
@SergiuIacob Czy constw każdym przypadku możemy użyć przed klasą bezpaństwową? A może to muszą być określone przypadki? Jeśli tak, jakie one są?
aytunch
1
@aytunch Nie sądzę, że można go używać constwszędzie. Na przykład, jeśli masz StatelessWidgetklasę, która zwraca a Textzawierającą wartość zmiennej i ta zmienna gdzieś się zmienia, to twoja StatelessWidgetpowinna zostać przebudowana, więc może pokazać tę inną wartość, dlatego nie może być const. Myślę, że można to bezpiecznie ująć w następujący sposób: gdziekolwiek możesz, używaj const, jeśli jest to bezpieczne.
Sergiu Iacob
3
Zastanawiam się, czy samemu odpowiedzieć na to pytanie. Przyjęta odpowiedź jest po prostu błędna, ale Rémi zrobił wiele, aby pomóc trzepoczącej społeczności, więc ludzie prawdopodobnie nie analizują jego odpowiedzi tak bardzo, jak kogoś innego. To może być oczywiste po wszystkich pozytywnych opiniach. Ludzie chcą tylko „jednego źródła prawdy”. :-)
DarkNeuron
4

Istniała duża różnica między tym, co robi funkcje, a tym, co robi klasa.


Pozwólcie, że wyjaśnię to od podstaw. (Tylko o imperatyw)

  • Historia programowania, którą wszyscy znamy, rozpoczęła się od prostych poleceń podstawowych (np. -: Assembly).

  • Next Strukturalne programowanie przyszło z kontrolkami przepływu (np .-: if, switch, while, for etc) Ten paradygmat daje programistom efektywną kontrolę przepływu programu, a także minimalizuje liczbę linii kodu przez pętle.

  • Następnie przyszło programowanie proceduralne, które grupuje instrukcje w procedury (funkcje). Dało to dwie główne korzyści dla programistów.

    1. Grupuj instrukcje (operacje) w oddzielnych blokach.

    2. Potrafi ponownie wykorzystać te bloki. (Funkcje)

Ale przede wszystkim paradygmaty nie dawały rozwiązania do zarządzania aplikacjami. Programowanie proceduralne może być również używane tylko w aplikacjach na małą skalę. Nie można tego używać do tworzenia dużych aplikacji internetowych (np.: Bankowość, Google, youtube, facebook, stackoverflow itp.), Nie można tworzyć frameworków, takich jak android sdk, flutter sdk i wiele więcej ......

Dlatego inżynierowie przeprowadzają znacznie więcej badań, aby właściwie zarządzać programami.

  • Wreszcie programowanie obiektowe zawiera całe rozwiązanie do zarządzania aplikacjami w dowolnej skali (od hello world do bilionów ludzi korzystających z tworzenia systemów, np.-Google, amazon, a dziś 90% aplikacji).

  • W oop wszystkie aplikacje są zbudowane wokół obiektów, co oznacza, że ​​aplikacja jest zbiorem tych obiektów.

więc obiekty są podstawowym budynkiem dla każdej aplikacji.

class (obiekt w czasie wykonywania) grupuje dane i funkcje związane z tymi zmiennymi (dane). więc obiekt składa się z danych i powiązanych z nimi operacji.

[Tutaj nie mam zamiaru wyjaśniać oop]


👉👉👉Ok Now Lets come for flutter framework.

-Dart obsługuje zarówno procedury, jak i oop Ale framework Flutter jest całkowicie zbudowany przy użyciu klas (oop). (Ponieważ duże, łatwe w zarządzaniu ramy nie mogą tworzyć przy użyciu procedur)

Tutaj utworzę listę powodów, dla których używają klas zamiast funkcji do tworzenia widżetów


1 - W większości przypadków metoda budowania (widget potomny) wywołuje numer funkcji synchronicznej i asynchronicznej.

Dawny:

  • Aby pobrać obraz sieciowy
  • uzyskać dane wejściowe od użytkownika itp.

więc metoda budowania musi być przechowywana w osobnym widżecie klasy (ponieważ wszystkie inne metody wywoływane przez metodę build () mogą przechowywać się w jednej klasie)


2 - Korzystając z klasy widżetu możesz utworzyć numer innej klasy bez ponownego pisania tego samego kodu (** Use Of Inheritance ** (extends)).

A także używając dziedziczenia (rozszerzanie) i polimorfizmu (nadpisywanie) możesz stworzyć własną klasę niestandardową. (Przykład poniżej, w tym miejscu dostosuję (Zastąp) animację, rozszerzając MaterialPageRoute (ponieważ domyślne przejście chcę dostosować).

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 - Funkcje nie mogą dodawać warunków do swoich parametrów, ale możesz to zrobić za pomocą konstruktora widżetu klasy.

Poniżej przykład kodu👇 (ta funkcja jest intensywnie używana przez widżety frameworka)

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 - Funkcje nie mogą używać stałej, a widżet klasy może używać stałej dla swoich konstruktorów. (które wpływają na wydajność głównego wątku)


5 - Możesz utworzyć dowolną liczbę niezależnych widżetów przy użyciu tej samej klasy (instancji klasy / obiektów), ale funkcja nie może tworzyć niezależnych widżetów (instancji), ale może to zrobić ponownie.

[każda instancja ma własną zmienną instancji, która jest całkowicie niezależna od innych widżetów (obiektów), ale zmienna lokalna funkcji jest zależna od każdego wywołania funkcji * (co oznacza, że ​​zmiana wartości zmiennej lokalnej wpływa na wszystkie inne części aplikacja korzystająca z tej funkcji)]


Klasa miała wiele zalet w porównaniu z funkcjami ... (powyżej podano tylko kilka przypadków użycia)


🤯 Moja ostatnia myśl

Dlatego nie używaj funkcji jako elementu konstrukcyjnego aplikacji, używaj ich tylko do wykonywania operacji. W przeciwnym razie, gdy aplikacja stanie się skalowalna, spowoduje wiele nieporęcznych problemów .

  • Użyj funkcji do wykonania niewielkiej części zadania
  • Użyj klasy jako elementu składowego aplikacji (zarządzanie aplikacją)

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

NIE MOŻESZ ZMIERZYĆ JAKOŚCI PROGRAMU WEDŁUG LICZBY WYRAŻEŃ (lub LINII).

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

Dziękuje za przeczytanie

TDM
źródło
Witamy w Stackoverflow! Nie jestem pewien, co próbujesz wyrazić swoją odpowiedzią. Możesz użyć funkcji dobrze do tworzenia widżetów. shrinkHelper() { return const SizedBox.shrink(); }działa tak samo, jak użycie const SizedBox.shrink()inline w drzewie widżetów, a korzystając z funkcji pomocniczych, możesz ograniczyć liczbę zagnieżdżeń w jednym miejscu.
DarkNeuron
@DarkNeuron Dzięki za udostępnienie. Spróbuję użyć funkcji pomocniczych.
TDM
2

Podczas wywoływania widżetu Flutter upewnij się, że używasz słowa kluczowego const. Na przykładconst MyListWidget();

user4761410
źródło
9
Czy mogę wiedzieć, jak to odpowiada na pytanie OP?
CopsOnRoad,
2
Wygląda na to, że odpowiedziałem, że jest to niewłaściwa sekcja. Próbowałem odpowiedzieć na pytanie Daniela, że ​​refaktoryzowana metoda budowania bezstanowego widżetu jest wciąż wywoływana. Dodając constsłowo kluczowe podczas wywoływania refaktoryzowanego bezstanowego widgetu, powinno ono zostać wywołane tylko raz.
user4761410
1
Dobrze. Rozumiem. Ludzie mogą odrzucać tę odpowiedź, ponieważ nie ma ona nic wspólnego z pytaniem OP. Więc powinieneś go usunąć. W każdym razie wybór należy do Ciebie.
CopsOnRoad