¿Cuál es la diferencia entre funciones y clases para crear widgets reutilizables?

125

Me he dado cuenta de que es posible crear widgets usando funciones simples en lugar de subclasificar StatelessWidget . Un ejemplo sería este:

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

Esto es interesante porque requiere mucho menos código que una clase completa. Ejemplo:

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

Entonces me he estado preguntando: ¿Existe alguna diferencia además de la sintaxis entre las funciones y las clases para crear widgets? ¿Y es una buena práctica utilizar funciones?

Rémi Rousselet
fuente
Encontré este hilo muy útil para comprender el problema. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Respuestas:

171

TL; DR: Prefiere usar clases sobre funciones para hacer un árbol de widgets reutilizable .


EDITAR : Para compensar algunos malentendidos: no se trata de funciones que causan problemas, sino de clases que resuelven algunos.

Flutter no tendría StatelessWidget si una función pudiera hacer lo mismo.

Asimismo, está dirigido principalmente a widgets públicos, hechos para ser reutilizados. No importa tanto que las funciones privadas diseñadas para usarse solo una vez, aunque ser consciente de este comportamiento sigue siendo bueno.


Hay una diferencia importante entre usar funciones en lugar de clases, es decir: el marco no conoce las funciones, pero puede ver las clases.

Considere la siguiente función de "widget":

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

usado de esta manera:

functionWidget(
  child: functionWidget(),
);

Y es equivalente de clase:

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

usado así:

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

Sobre el papel, ambos parecen hacer exactamente lo mismo: crear 2 Container, con uno anidado en el otro. Pero la realidad es un poco diferente.

En el caso de las funciones, el árbol de widgets generado se ve así:

Container
  Container

Mientras que con las clases, el árbol de widgets es:

ClassWidget
  Container
    ClassWidget
      Container

Esto es importante porque cambia la forma en que se comporta el marco al actualizar un widget.

Por que importa

Al usar funciones para dividir su árbol de widgets en múltiples widgets, se expone a errores y se pierde algunas optimizaciones de rendimiento.

No hay garantía de que va a tener errores mediante el uso de funciones, pero mediante el uso de clases, usted está garantizado para no hacer frente a estos problemas.

Aquí hay algunos ejemplos interactivos en Dartpad que puede ejecutar usted mismo para comprender mejor los problemas:

Conclusión

Aquí hay una lista seleccionada de las diferencias entre el uso de funciones y clases:

  1. Clases:
  • permitir la optimización del rendimiento (constructor const, reconstrucción más granular)
  • asegurarse de que el cambio entre dos diseños diferentes elimine correctamente los recursos (las funciones pueden reutilizar algún estado anterior)
  • garantiza que la recarga en caliente funcione correctamente (el uso de funciones podría interrumpir la recarga en caliente para showDialogsy similares)
  • están integrados en el inspector de widgets.
    • Vemos ClassWidgeten el árbol de widgets que muestra la herramienta devtool, que ayuda a comprender lo que hay en la pantalla
    • Podemos anular debugFillProperties para imprimir cuáles son los parámetros pasados ​​a un widget
  • mejores mensajes de error
    Si ocurre una excepción (como ProviderNotFound), el marco le dará el nombre del widget de construcción actual. Si ha dividido su árbol de widgets solo en funciones + Builder, sus errores no tendrán un nombre útil
  • puede definir claves
  • puede usar la API de contexto
  1. Funciones:
  • tener menos código (que se puede resolver usando el widget funcional de generación de código )

En general, se considera una mala práctica usar funciones sobre clases para reutilizar widgets debido a estas razones.
Usted puede , pero te puede morder en el futuro.

Rémi Rousselet
fuente
Los comentarios no son para una discusión extensa; esta conversación se ha movido al chat .
Samuel Liew
10

He estado investigando este problema durante los últimos 2 días. Llegué a la siguiente conclusión: está BIEN dividir partes de la aplicación en funciones. Es ideal que esas funciones devuelvan a StatelessWidget, para que se puedan realizar optimizaciones, como hacer StatelessWidget const, para que no se reconstruya si no es necesario. Por ejemplo, este fragmento de código es perfectamente válido:

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

El uso de la función allí está perfectamente bien, ya que devuelve un const StatelessWidget. Por favor corrígeme si estoy equivocado.

Sergiu Iacob
fuente
¿Alguien puede explicar por qué lo que dije está mal? Quiero decir, supongo que está mal dados los votos negativos.
Sergiu Iacob
De hecho estoy de acuerdo contigo. Tenía la intención de escribir un desglose mucho más detallado de las diferencias, pero no lo he logrado. Siéntase libre de desarrollar su argumento, ya que creo que es importante comprender los pros y los contras de los métodos de widgets.
TheIT
@SergiuIacob ¿Podemos usar constdelante de la clase sin estado para cada caso? ¿O tienen que ser ciertos casos? Si es así, ¿cuáles son?
Almuerzo
1
@aytunch No creo que puedas usarlo en consttodas partes. Por ejemplo, si tiene una StatelessWidgetclase que devuelve un que Textcontiene el valor de una variable, y esa variable cambia en algún lugar, entonces StatelessWidgetdebe reconstruirse, por lo que puede mostrar ese valor diferente, por lo tanto, no puede ser const. Creo que la forma segura de decirlo es esta: siempre que pueda, úselo const, si es seguro hacerlo.
Sergiu Iacob
3
He estado debatiendo si debo responder a esta pregunta yo mismo. La respuesta aceptada es sencillamente incorrecta, pero Rémi ha hecho mucho para intentar ayudar a la comunidad de aleteo, por lo que la gente probablemente no escudriña sus respuestas tanto como las de otra persona. Eso podría ser evidente por todos los votos a favor. La gente solo quiere su "única fuente de verdad". :-)
DarkNeuron
4

Había una gran diferencia entre lo que hace la función y lo que hace la clase.


Deja que te lo explique desde cero . 🙂 (solo sobre imperativo)

  • El historial de programación, todos sabemos que comenzó con comandos básicos directos (por ejemplo: Ensamblaje).

  • Siguiente La programación estructurada vino con controles de flujo (por ejemplo, si, cambiar, mientras, para, etc.) Este paradigma permite a los programadores controlar el flujo del programa de manera eficaz y también minimizar el número de líneas de código por bucles.

  • Luego vino la programación procedimental y que agrupa las instrucciones en procedimientos (funciones). Esto dio dos importantes beneficios para los programadores.

    1. Agrupe las declaraciones (operaciones) en bloques separados.

    2.Puede reutilizar estos bloques. (Funciones)

Pero sobre todo los paradigmas no dieron una solución para la gestión de aplicaciones. La programación por procedimientos también solo se puede utilizar para aplicaciones a pequeña escala. Eso no se puede usar para desarrollar aplicaciones web grandes (por ejemplo: banca, google, youtube, facebook, stackoverflow, etc.), no se pueden crear marcos como android sdk, flutter sdk y mucho más ...

De modo que los ingenieros investigan mucho más para gestionar los programas de forma adecuada.

  • Finalmente, la Programación Orientada a Objetos viene con todas las soluciones para administrar aplicaciones a cualquier escala (desde hello world hasta billones de personas que utilizan la creación de sistemas, por ejemplo, google, amazon y hoy el 90% de las aplicaciones).

  • En oop, todas las aplicaciones se basan en objetos, lo que significa que la aplicación es una colección de estos objetos.

por lo que los objetos son el edificio básico para cualquier aplicación.

clase (objeto en tiempo de ejecución) grupo de datos y funciones relacionadas con esas variables (datos). para que el objeto se componga de datos y sus operaciones relacionadas.

[Aquí no voy a explicar sobre oop]


👉👉👉Ok Now Vayamos para flutter framework.👈👈👈

-Dart soporta tanto procedural como oop Pero, el framework Flutter se construye completamente usando clases (oop). (Porque un gran marco manejable no se puede crear usando procedimientos)

Aquí crearé una lista de razones por las que usan clases en lugar de funciones para hacer widgets.


1 - La mayoría de las veces, el método de construcción (widget secundario) llama al número de funciones sincrónicas y asincrónicas.

Ex:

  • Para descargar la imagen de red
  • obtener información del usuario, etc.

por lo que el método de compilación debe mantenerse en un widget de clase separado (porque todos los demás métodos llamados por el método build () pueden mantenerse en una clase)


2 - Usando la clase de widget, puede crear un número de otra clase sin escribir el mismo código una y otra vez (** Uso de herencia ** (extiende)).

Y también usando herencia (extender) y polimorfismo (anular) puede crear su propia clase personalizada. (Abajo del ejemplo, allí personalizaré (Anularé) la animación extendiendo MaterialPageRoute (porque quiero personalizar su transición predeterminada) .👇

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 - Las funciones no pueden agregar condiciones para sus parámetros, pero usando el constructor del widget de clase puede hacerlo.

Ejemplo de código a continuación👇 (esta función es muy utilizada por los widgets de marco)

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 - Las funciones no pueden usar const y el widget Class puede usar const para sus constructores. (que afectan el rendimiento del hilo principal)


5 - Puede crear cualquier número de widgets independientes usando la misma clase (instancias de una clase / objetos) Pero la función no puede crear widgets independientes (instancia), pero la reutilización sí.

[cada instancia tiene su propia variable de instancia y eso es completamente independiente de otros widgets (objeto), pero la variable local de la función depende de cada llamada de función * (lo que significa que cuando cambia un valor de una variable local afecta a todas las demás partes de la aplicación que utiliza esta función)]


Hubo muchas ventajas en la clase sobre las funciones ... (arriba son solo algunos casos de uso)


🤯 Mi pensamiento final

Por lo tanto, no use las funciones como bloques de construcción de su aplicación, úselas solo para realizar operaciones. De lo contrario, causa muchos problemas imposibles de manejar cuando su aplicación se vuelve escalable .

  • Utilice funciones para realizar una pequeña parte de la tarea
  • Usar la clase como componente básico de una aplicación (administración de la aplicación)

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

NO SE PUEDE MEDIR LA CALIDAD DEL PROGRAMA POR EL NÚMERO DE DECLARACIONES (o líneas) que utiliza

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

Gracias por leer

TDM
fuente
¡Bienvenido a Stackoverflow! No estoy muy seguro de lo que intentas expresar con tu respuesta. Puede utilizar una función sin problemas para crear widgets. shrinkHelper() { return const SizedBox.shrink(); }es lo mismo que usar const SizedBox.shrink()en línea en su árbol de widgets, y al usar funciones auxiliares puede limitar la cantidad de anidamiento en un lugar.
DarkNeuron
@DarkNeuron Gracias por compartir. Intentaré usar funciones auxiliares.
TDM
2

Cuando llame al widget Flutter, asegúrese de usar la palabra clave const. Por ejemploconst MyListWidget();

usuario4761410
fuente
9
¿Puedo saber cómo responde esto a la pregunta OP?
CopsOnRoad
2
Parece que respondí que la sección incorrecta. Estaba tratando de responder a la pregunta de Daniel de que todavía se está llamando al método de construcción de widget sin estado refactorizado. Al agregar la constpalabra clave al llamar al widget sin estado refactorizado, solo se debe llamar una vez.
user4761410
1
Okay. Entendido. La gente puede rechazar esta respuesta ya que no tiene nada que ver con la pregunta OP. Entonces deberías borrarlo. De todos modos, la elección es tuya.
CopsOnRoad