Qual è la differenza tra funzioni e classi per creare widget riutilizzabili?


125

Mi sono reso conto che è possibile creare widget utilizzando funzioni semplici invece di creare sottoclassi StatelessWidget . Un esempio potrebbe essere questo:

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

Questo è interessante perché richiede molto meno codice di una classe completa. Esempio:

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

Quindi mi sono chiesto: c'è qualche differenza oltre alla sintassi tra funzioni e classi per creare widget? Ed è una buona pratica usare le funzioni?


Ho trovato questo thread molto utile per la mia comprensione del problema. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Risposte:


172

TL; DR: preferisce usare le classi rispetto alle funzioni per rendere l' albero dei widget riutilizzabile .


EDIT : Per sopperire a qualche malinteso: non si tratta di funzioni che causano problemi, ma di classi che ne risolvono alcuni.

Flutter non avrebbe StatelessWidget se una funzione potesse fare la stessa cosa.

Allo stesso modo, è principalmente diretto a widget pubblici, fatti per essere riutilizzati. Non importa tanto per le funzioni private create per essere utilizzate una sola volta, anche se essere consapevoli di questo comportamento è ancora buono.


C'è un'importante differenza tra l'utilizzo delle funzioni invece delle classi, ovvero: il framework non è a conoscenza delle funzioni, ma può vedere le classi.

Considera la seguente funzione "widget":

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

usato in questo modo:

functionWidget(
  child: functionWidget(),
);

Ed è l'equivalente di una classe:

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

usato così:

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

Sulla carta, entrambi sembrano fare esattamente la stessa cosa: creare 2 Container, con uno annidato nell'altro. Ma la realtà è leggermente diversa.

Nel caso delle funzioni, l'albero dei widget generato è simile a questo:

Container
  Container

Mentre con le classi, l'albero dei widget è:

ClassWidget
  Container
    ClassWidget
      Container

Questo è importante perché cambia il comportamento del framework durante l'aggiornamento di un widget.

Perché è importante

Utilizzando le funzioni per dividere il tuo albero dei widget in più widget, ti esponi a bug e perdi alcune ottimizzazioni delle prestazioni.

Non vi è alcuna garanzia che si verifichino bug utilizzando le funzioni, ma utilizzando le classi, si è sicuri di non affrontare questi problemi.

Ecco alcuni esempi interattivi su Dartpad che puoi eseguire tu stesso per comprendere meglio i problemi:

Conclusione

Ecco un elenco curato delle differenze tra l'utilizzo di funzioni e classi:

  1. Classi:
  • consentire l'ottimizzazione delle prestazioni (costruttore const, ricostruzione più granulare)
  • assicurarsi che il passaggio tra due diversi layout elimini correttamente le risorse (le funzioni potrebbero riutilizzare alcuni stati precedenti)
  • assicura che la ricarica a caldo funzioni correttamente (l'uso di funzioni potrebbe interrompere la ricarica a caldo per showDialogs& simili)
  • sono integrati nell'ispettore widget.
    • Vediamo ClassWidgetnell'albero dei widget mostrato dal devtool, che aiuta a capire cosa c'è sullo schermo
    • Possiamo sovrascrivere debugFillProperties per stampare quali sono i parametri passati a un widget
  • messaggi di errore migliori
    Se si verifica un'eccezione (come ProviderNotFound), il framework ti darà il nome del widget attualmente in costruzione. Se hai diviso l'albero dei widget solo in functions + Builder, i tuoi errori non avranno un nome utile
  • può definire le chiavi
  • può utilizzare l'API di contesto
  1. funzioni:
  • avere meno codice (che può essere risolto utilizzando la generazione del codice function_widget )

Nel complesso, è considerata una cattiva pratica usare le funzioni rispetto alle classi per riutilizzare i widget a causa di questi motivi.
È possibile , ma può mordere in futuro.


I commenti non sono per discussioni estese; questa conversazione è stata spostata nella chat .
Samuel Liew

10

Ho fatto ricerche su questo problema negli ultimi 2 giorni. Sono giunto alla seguente conclusione: va bene suddividere parti dell'app in funzioni. È semplicemente ideale che quelle funzioni restituiscano un StatelessWidget, quindi è possibile apportare ottimizzazioni, come creare il StatelessWidget const, in modo che non si ricostruisca se non è necessario. Ad esempio, questo pezzo di codice è perfettamente valido:

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

L'uso della funzione è perfettamente corretto, poiché restituisce un file const StatelessWidget. Per favore correggimi se sbaglio.


Qualcuno può spiegare perché quello che ho detto è sbagliato? Voglio dire, suppongo che sia sbagliato visti i voti negativi.
Sergiu Iacob

Sono proprio d'accordo con te. Avevo intenzione di scrivere una ripartizione molto più dettagliata delle differenze, ma non ci sono riuscito. Sentiti libero di arricchire la tua argomentazione perché penso che sia importante comprendere i pro ei contro dei metodi dei widget v.
TheIT

@SergiuIacob Possiamo usare constdavanti alla classe senza stato per ogni caso? O devono essere determinati casi? Se sì, cosa sono?
aytunch

1
@aytunch Non penso che tu possa usare constovunque. Ad esempio, se hai una StatelessWidgetclasse che restituisce a Textcontenente il valore di una variabile e quella variabile cambia da qualche parte, allora StatelessWidgetdovresti ricostruirla, in modo che possa mostrare quel valore diverso, quindi non può essere const. Penso che il modo sicuro per dirlo sia questo: ovunque puoi, usa const, se è sicuro farlo.
Sergiu Iacob

3
Ho discusso se rispondere a questa domanda io stesso. La risposta accettata è chiaramente sbagliata, ma Rémi ha fatto molto per cercare di aiutare la comunità di flutter, quindi le persone probabilmente non esaminano le sue risposte tanto quanto quelle di qualcun altro. Ciò potrebbe essere evidente da tutti i voti positivi. Le persone vogliono solo la loro "unica fonte di verità". :-)
DarkNeuron

4

C'era una grande differenza tra ciò che fa le funzioni e ciò che fa la classe.


Consente di spiegarlo da zero . 🙂 (solo imperativo)

  • La storia della programmazione, sappiamo tutti, è iniziata con semplici comandi di base (ad esempio: Assembly).

  • Next La programmazione strutturata è arrivata con i controlli di flusso (ad esempio: if, switch, while, for ecc.) Questo paradigma consente ai programmatori di controllare il flusso del programma in modo efficace e anche di ridurre al minimo il numero di righe di codice per loop.

  • Successivamente è arrivata la programmazione procedurale che raggruppa le istruzioni in procedure (funzioni). Ciò ha dato due vantaggi principali ai programmatori.

    1. Raggruppa le istruzioni (operazioni) in blocchi separati.

    2.Può riutilizzare questi blocchi. (Funzioni)

Ma soprattutto i paradigmi non hanno fornito una soluzione per la gestione delle applicazioni. La programmazione procedurale può anche essere utilizzata solo per applicazioni su piccola scala. Non può essere utilizzato per sviluppare applicazioni web di grandi dimensioni (es: banking, google, youtube, facebook, stackoverflow ecc.), Non può creare framework come android sdk, flutter sdk e molto altro ...

Quindi gli ingegneri fanno molte più ricerche per gestire i programmi in modo corretto.

  • Infine la programmazione orientata agli oggetti viene fornita con tutte le soluzioni per la gestione delle applicazioni su qualsiasi scala (da Hello World a trilioni di persone che utilizzano la creazione di sistemi, ad esempio google, amazon e oggi il 90% delle applicazioni).

  • In oop tutte le applicazioni sono costruite intorno agli oggetti, significa che l'applicazione è una raccolta di questi oggetti.

quindi gli oggetti sono l'edificio di base per qualsiasi applicazione.

class (oggetto in fase di esecuzione) raggruppa i dati e le funzioni relative a tali variabili (dati). quindi oggetto della composizione dei dati e delle relative operazioni.

[Qui non ho intenzione di spiegare su oop]


👉👉👉Ok Now Lets coming for flutter framework.👈👈👈

-Dart supporta sia procedurale che oop Ma, il framework Flutter viene costruito completamente utilizzando le classi (oop). (Perché un framework gestibile di grandi dimensioni non può creare utilizzando procedurale)

Qui creerò un elenco di motivi per cui usano le classi invece delle funzioni per creare widget.👇👇👇


1 - La maggior parte delle volte il metodo build (widget figlio) chiama il numero di funzioni sincrone e asincrone.

Ex:

  • Per scaricare l'immagine di rete
  • ottenere input dall'utente ecc.

quindi il metodo build deve essere mantenuto in un widget di classe separato (perché tutti gli altri metodi chiamati dal metodo build () possono rimanere in una classe)


2 - Usando la classe widget puoi creare il numero di un'altra classe senza scrivere lo stesso codice ancora e ancora (** Use Of Inheritance ** (extends)).

E anche usando l'ereditarietà (estensione) e il polimorfismo (sovrascrittura) è possibile creare una propria classe personalizzata. (In basso nell'esempio, in là personalizzerò (sovrascriverò) l'animazione estendendo MaterialPageRoute (perché la sua transizione predefinita voglio personalizzare).

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 - Le funzioni non possono aggiungere condizioni per i loro parametri, ma usando il costruttore del widget di classe Puoi farlo.

Sotto Esempio di codice👇 (questa funzione è ampiamente utilizzata dai widget del framework)

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 - Le funzioni non possono usare const e il widget Class può usare const per i loro costruttori. (che influenzano le prestazioni del thread principale)


5 - Puoi creare un numero qualsiasi di widget indipendenti usando la stessa classe (istanze di una classe / oggetti) Ma la funzione non può creare widget indipendenti (istanza), ma il riutilizzo sì.

[ogni istanza ha la propria variabile di istanza e quella completamente indipendente dagli altri widget (oggetto), ma la variabile locale della funzione dipende da ciascuna chiamata di funzione * (il che significa che quando si modifica un valore di una variabile locale, essa influisce su tutte le altre parti di l'applicazione che utilizza questa funzione)]


C'erano molti vantaggi in classe rispetto alle funzioni ... (sopra sono solo pochi casi d'uso)


🤯 Il mio pensiero finale

Quindi non utilizzare le funzioni come elemento costitutivo della tua applicazione, usale solo per eseguire operazioni. Altrimenti causa molti problemi non gestibili quando l'applicazione diventa scalabile .

  • Utilizzare le funzioni per eseguire piccole porzioni di attività
  • Usa la classe come elemento costitutivo di un'applicazione (Gestione dell'applicazione)

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

NON È POSSIBILE MISURARE LA QUALITÀ DEL PROGRAMMA PER IL NUMERO DI DICHIARAZIONI (o righe) UTILIZZATE DA esso

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

Grazie per aver letto


Benvenuto in Stackoverflow! Non sono davvero sicuro di cosa stai cercando di esprimere con la tua risposta. Puoi usare una funzione benissimo per la creazione di widget. shrinkHelper() { return const SizedBox.shrink(); }è lo stesso che utilizzare const SizedBox.shrink()inline nella struttura ad albero dei widget e utilizzando le funzioni di supporto è possibile limitare la quantità di annidamento in un unico punto.
DarkNeuron

@DarkNeuron Grazie per la condivisione. Proverò a utilizzare le funzioni di supporto.
TDM

2

Quando chiami il widget Flutter assicurati di utilizzare la parola chiave const. Per esempioconst MyListWidget();


9
Posso sapere come questo risponde alla domanda OP?
CopsOnRoad

2
Sembra che io abbia risposto nella sezione sbagliata. Stavo cercando di rispondere alla domanda di Daniel secondo cui il metodo di compilazione del widget stateless refactored viene ancora chiamato. Aggiungendo la constparola chiave quando si chiama il widget senza stato refactoring, dovrebbe essere chiamato solo una volta.
user4761410

1
Ok. Fatto. Le persone potrebbero sottovalutare questa risposta in quanto non ha nulla a che fare con la domanda OP. Quindi dovresti eliminarlo. Comunque la scelta è tua.
CopsOnRoad
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.