Quelle est la différence entre les fonctions et les classes pour créer des widgets réutilisables?

125

J'ai réalisé qu'il est possible de créer des widgets en utilisant des fonctions simples au lieu de sous- classer StatelessWidget . Un exemple serait celui-ci:

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

Ceci est intéressant car il nécessite beaucoup moins de code qu'une classe à part entière. Exemple:

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

Je me suis donc demandé: y a-t-il une différence en plus de la syntaxe entre les fonctions et les classes pour créer des widgets? Et est-ce une bonne pratique d'utiliser des fonctions?

Rémi Rousselet
la source
J'ai trouvé ce fil très utile pour ma compréhension du problème. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Réponses:

172

TL; DR: Préférez utiliser des classes plutôt que des fonctions pour créer une arborescence de widgets réutilisable .


EDIT : Pour compenser certains malentendus: il ne s'agit pas de fonctions causant des problèmes, mais de classes en résolvant certains.

Flutter n'aurait pas StatelessWidget si une fonction pouvait faire la même chose.

De même, il s'adresse principalement aux widgets publics, faits pour être réutilisés. Cela n'a pas autant d'importance pour les fonctions privées conçues pour être utilisées qu'une seule fois - bien qu'il soit toujours bon d'être conscient de ce comportement.


Il existe une différence importante entre l'utilisation de fonctions au lieu de classes, c'est-à-dire: le framework ne connaît pas les fonctions, mais peut voir les classes.

Considérez la fonction "widget" suivante:

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

utilisé de cette façon:

functionWidget(
  child: functionWidget(),
);

Et c'est l'équivalent de la 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,
    );
  }
}

utilisé comme ça:

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

Sur le papier, les deux semblent faire exactement la même chose: Create 2 Container, avec l'un imbriqué dans l'autre. Mais la réalité est légèrement différente.

Dans le cas des fonctions, l'arborescence de widgets générée ressemble à ceci:

Container
  Container

Alors qu'avec les classes, l'arborescence des widgets est:

ClassWidget
  Container
    ClassWidget
      Container

Ceci est important car cela change le comportement du framework lors de la mise à jour d'un widget.

Pourquoi c'est important

En utilisant des fonctions pour diviser votre arborescence de widgets en plusieurs widgets, vous vous exposez à des bogues et manquez certaines optimisations de performances.

Il n'y a aucune garantie que vous aurez des bogues en utilisant des fonctions, mais en utilisant des classes, vous êtes assuré de ne pas faire face à ces problèmes.

Voici quelques exemples interactifs sur Dartpad que vous pouvez exécuter vous-même pour mieux comprendre les problèmes:

Conclusion

Voici une liste organisée des différences entre l'utilisation de fonctions et de classes:

  1. Des classes:
  • permettre l'optimisation des performances (constructeur const, reconstruction plus granulaire)
  • s'assurer que la commutation entre deux dispositions différentes élimine correctement les ressources (les fonctions peuvent réutiliser un état précédent)
  • garantit que le rechargement à chaud fonctionne correctement (l'utilisation de fonctions pourrait interrompre le rechargement à chaud pour showDialogset similaire)
  • sont intégrés dans l'inspecteur de widgets.
    • Nous voyons ClassWidgetdans l'arborescence des widgets montrée par le devtool, qui aide à comprendre ce qui est à l'écran
    • Nous pouvons remplacer debugFillProperties pour afficher les paramètres passés à un widget
  • meilleurs messages d'erreur
    Si une exception se produit (comme ProviderNotFound), le framework vous donnera le nom du widget en cours de construction. Si vous avez divisé votre arborescence de widgets uniquement en fonctions + Builder, vos erreurs n'auront pas de nom utile
  • peut définir des clés
  • peut utiliser l'API contextuelle
  1. Les fonctions:
  • avoir moins de code (qui peut être résolu en utilisant la génération de code fonctionnelle_widget )

Dans l'ensemble, il est considéré comme une mauvaise pratique d'utiliser des fonctions sur des classes pour réutiliser des widgets pour ces raisons.
Vous pouvez , mais cela peut vous mordre à l'avenir.

Rémi Rousselet
la source
Les commentaires ne sont pas destinés à une discussion approfondie; cette conversation a été déplacée vers le chat .
Samuel Liew
10

Je fais des recherches sur ce problème depuis 2 jours. Je suis arrivé à la conclusion suivante: il est OK de décomposer des éléments de l'application en fonctions. Il est juste idéal que ces fonctions renvoient a StatelessWidget, donc des optimisations peuvent être faites, comme faire le StatelessWidget const, afin qu'il ne se reconstruise pas si ce n'est pas nécessaire. Par exemple, ce morceau de code est parfaitement valide:

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'utilisation de la fonction y est parfaitement correcte, car elle renvoie un fichier const StatelessWidget. Corrigez-moi si j'ai tort, s'il-vous plait.

Sergiu Iacob
la source
Quelqu'un peut-il expliquer pourquoi ce que j'ai dit est faux? Je veux dire, je suppose que c'est faux compte tenu des votes négatifs.
Sergiu Iacob
En fait, je suis d'accord avec toi. J'avais l'intention d'écrire une ventilation beaucoup plus détaillée des différences, mais je n'y suis pas parvenu. N'hésitez pas à étoffer votre argument car je pense qu'il est important de comprendre les avantages et les inconvénients des widgets et des méthodes.
TheIT
@SergiuIacob Peut-on utiliser constdevant la classe sans état pour chaque cas? Ou faut-il que ce soit certains cas? Si oui, quels sont-ils?
aytunch
1
@aytunch Je ne pense pas que vous puissiez l'utiliser constpartout. Par exemple, si vous avez une StatelessWidgetclasse qui renvoie une Textcontenant la valeur d'une variable, et que cette variable change quelque part, alors vous StatelessWidgetdevriez être reconstruite pour qu'elle puisse afficher cette valeur différente, donc elle ne peut pas l'être const. Je pense que le moyen le plus sûr de le mettre est le suivant: partout où vous le pouvez, utilisez-le const, si cela est sécuritaire.
Sergiu Iacob
3
Je me suis demandé si je devais répondre moi-même à cette question. La réponse acceptée est tout à fait fausse, mais Rémi a fait beaucoup pour essayer d'aider la communauté flottante, donc les gens ne scrutent probablement pas ses réponses autant que celles de quelqu'un d'autre. Cela pourrait être évident à partir de tous les votes positifs. Les gens veulent juste leur «source unique de vérité». :-)
DarkNeuron
4

Il y avait une grande différence entre ce que font les fonctions et ce que fait la classe.


Permettez-moi de l'expliquer à partir de zéro. (Uniquement à propos de l'impératif)

  • L'historique de la programmation, nous le savons tous, a commencé avec des commandes simples de base (par exemple: Assemblage).

  • La programmation structurée suivante est venue avec des contrôles de flux (par exemple: if, switch, while, for etc). Ce paradigme permet aux programmeurs de contrôler efficacement le flux du programme et aussi de minimiser le nombre de lignes de code par des boucles.

  • Ensuite, la programmation procédurale est venue et qui regroupe les instructions en procédures (fonctions). Cela a donné deux avantages majeurs aux programmeurs.

    1. Groupez les instructions (opérations) en blocs séparés.

    2.Peut réutiliser ces blocs. (Fonctions)

Mais surtout, les paradigmes ne donnaient pas de solution pour la gestion des applications. La programmation procédurale ne peut également être utilisée que pour des applications à petite échelle. Cela ne peut pas être utilisé pour développer de grandes applications Web (par exemple: banque, google, youtube, facebook, stackoverflow, etc.), ne peut pas créer de frameworks comme android sdk, flutter sdk et bien plus encore ......

Les ingénieurs font donc beaucoup plus de recherches pour gérer correctement les programmes.

  • Enfin, la programmation orientée objet est livrée avec toute la solution pour gérer des applications à toutes les échelles (de hello world à des milliards de personnes utilisant la création de système, par exemple, google, amazon et aujourd'hui 90% des applications).

  • Dans oop, toutes les applications sont construites autour d'objets, cela signifie que l'application est une collection de ces objets.

les objets sont donc le bâtiment de base pour toute application.

class (objet lors de l'exécution) regroupe les données et les fonctions liées à ces variables (données). donc objet compose de données et de leurs opérations connexes.

[Ici, je ne vais pas expliquer à propos de oop]


👉👉👉Ok Now Permet de venir pour le cadre de flutter.👈👈👈

-Dart prend en charge à la fois procédural et oop Mais, le framework Flutter est entièrement construit en utilisant des classes (oop). (Parce qu'un grand cadre gérable ne peut pas créer à l'aide de procédures)

Ici, je vais créer une liste des raisons pour lesquelles ils utilisent des classes à la place des fonctions pour créer des widgets.


1 - La plupart du temps, la méthode de construction (widget enfant) appelle le numéro des fonctions synchrones et asynchrones.

Ex:

  • Pour télécharger l'image du réseau
  • obtenir l'entrée de l'utilisateur, etc.

donc la méthode de construction doit rester dans un widget de classe séparé (car toutes les autres méthodes appelées par la méthode build () peuvent rester dans une classe)


2 - En utilisant la classe de widget, vous pouvez créer le numéro d'une autre classe sans écrire le même code encore et encore (** Use Of Inheritance ** (extend)).

Et aussi en utilisant l'héritage (étendre) et le polymorphisme (remplacement), vous pouvez créer votre propre classe personnalisée. (Dans l'exemple ci-dessous, je vais personnaliser (remplacer) l'animation en étendant MaterialPageRoute (parce que sa transition par défaut que je veux personnaliser).

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 - Les fonctions ne peuvent pas ajouter de conditions pour leurs paramètres, mais en utilisant le constructeur du widget de classe Vous pouvez le faire.

Ci-dessous l'exemple de code👇 (cette fonctionnalité est fortement utilisée par les widgets du 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 - Les fonctions ne peuvent pas utiliser const et le widget Class peut utiliser le const pour leurs constructeurs. (qui affectent les performances du thread principal)


5 - Vous pouvez créer n'importe quel nombre de widgets indépendants en utilisant la même classe (instances d'une classe / objets) Mais la fonction ne peut pas créer de widgets indépendants (instance), mais la réutilisation le peut.

[chaque instance a sa propre variable d'instance et totalement indépendante des autres widgets (objet), mais la variable locale de la fonction dépend de chaque appel de fonction * (ce qui signifie que lorsque vous modifiez une valeur d'une variable locale, cela affecte toutes les autres parties de l'application qui utilise cette fonction)]


Il y avait de nombreux avantages en classe par rapport aux fonctions. (Ci-dessus ne sont que quelques cas d'utilisation)


🤯 Ma dernière pensée

N'utilisez donc pas les fonctions comme élément constitutif de votre application, utilisez-les uniquement pour effectuer des opérations. Sinon, cela pose de nombreux problèmes difficiles à gérer lorsque votre application devient évolutive .

  • Utilisez des fonctions pour effectuer une petite partie de la tâche
  • Utiliser la classe comme élément constitutif d'une application (gestion de l'application)

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

VOUS NE POUVEZ PAS MESURER LA QUALITÉ DU PROGRAMME PAR NOMBRE DE DÉCLARATIONS (ou de lignes) UTILISÉES PAR lui

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

Merci d'avoir lu

TDM
la source
Bienvenue dans Stackoverflow! Je ne sais pas vraiment ce que vous essayez d'exprimer avec votre réponse. Vous pouvez utiliser une fonction très bien pour créer des widgets. shrinkHelper() { return const SizedBox.shrink(); }est identique à l'utilisation en const SizedBox.shrink()ligne dans votre arborescence de widgets, et en utilisant les fonctions d'assistance, vous pouvez limiter la quantité d'imbrication en un seul endroit.
DarkNeuron
@DarkNeuron Merci pour le partage. J'essaierai d'utiliser des fonctions d'assistance.
TDM
2

Lorsque vous appelez le widget Flutter, assurez-vous d'utiliser le mot-clé const. Par exempleconst MyListWidget();

user4761410
la source
9
Puis-je savoir comment cela répond à la question du PO?
CopsOnRoad
2
On dirait que j'ai répondu à la mauvaise section. J'essayais de répondre à la question de Daniel selon laquelle la méthode de construction de widget sans état refactorisée est toujours appelée. En ajoutant le constmot - clé lors de l'appel du widget sans état refactoré, il ne doit être appelé qu'une seule fois.
user4761410
1
D'accord. Je l'ai. Les gens peuvent décliner cette réponse car elle n'a rien à voir avec la question OP. Vous devriez donc le supprimer. Quoi qu'il en soit, le choix vous appartient.
CopsOnRoad