web-dev-qa-db-fra.com

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

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 ceci:

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

Ceci est intéressant car il nécessite loin moins de code qu'une classe complète. 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é: en dehors de la syntaxe, existe-t-il une différence entre les fonctions et les classes pour créer des widgets? Et est-ce une bonne pratique d’utiliser des fonctions?

57
Rémi Rousselet

TL; DR: N'utilisez jamais de fonctions sur des classes pour créer un widget-tree réutilisable . Extrayez-les toujours dans un StatelessWidget .


Il y a une différence énorme entre utiliser des fonctions plutôt que des classes, c'est-à-dire que: le framework ignore 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 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: créez 2 Container, l'un imbriqué dans l'autre. Mais la réalité est légèrement différente.

Dans le cas de fonctions, l’arborescence des widgets générés se présente comme suit:

Container
  Container

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

ClassWidget
  Container
    ClassWidget
      Container

Ceci est très car il modifie radicalement le comportement du framework lors de la mise à jour d'un widget. Voici une liste organisée des différences:

  1. Des classes:

    • permettre l'optimisation des performances (constructeur const, opérateur == redéfinition, reconstruction plus granulaire)
    • avoir un rechargement à chaud
    • sont intégrés à l'inspecteur de widgets (debugFillProperties)
    • peut définir des clés
    • peut utiliser l'API de contexte
    • s'assurer que tous les widgets sont utilisés de la même manière (toujours un constructeur)
    • s'assurer que la commutation entre deux présentations différentes dispose correctement les ressources (les fonctions peuvent réutiliser un état antérieur)
  2. Les fonctions:

    • avoir moins de code (et même là, j'ai fait un générateur de code pour faire des classes aussi petites que des fonctions: functional_widget )
    • ?

La conclusion devrait être assez claire déjà:

N'utilisez pas de fonctions pour créer des widgets .

82
Rémi Rousselet

Je fais des recherches sur cette question depuis 2 jours. Je suis arrivé à la conclusion suivante: il est normal de décomposer des éléments de l'application en fonctions. Il est tout simplement idéal que ces fonctions renvoient un StatelessWidget. Vous pouvez donc effectuer des optimisations, telles que rendre le StatelessWidgetconst, afin qu'il ne soit pas reconstruit s'il ne le doit pas. 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 function y est parfaitement correcte, car elle renvoie un const StatelessWidget. Corrigez-moi si j'ai tort, s'il-vous plait.

3
Sergiu Iacob