Qual é a diferença entre funções e classes para criar widgets reutilizáveis?

125

Percebi que é possível criar widgets usando funções simples em vez de criar uma subclasse de StatelessWidget . Um exemplo seria este:

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

Isso é interessante porque requer muito menos código do que uma classe completa. Exemplo:

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

Então, estive me perguntando: Existe alguma diferença além da sintaxe entre funções e classes para criar widgets? E é uma boa prática usar funções?

Rémi Rousselet
fonte
Achei este tópico muito útil para minha compreensão do problema. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Respostas:

172

TL; DR: Prefira o uso de classes em vez de funções para tornar a árvore de widget reutilizável .


EDITAR : Para compensar alguns mal-entendidos: Não se trata de funções que causam problemas, mas de classes que resolvem alguns.

Flutter não teria StatelessWidget se uma função pudesse fazer a mesma coisa.

Da mesma forma, é direcionado principalmente para widgets públicos, feitos para serem reutilizados. Não importa tanto para funções privadas feitas para serem usadas apenas uma vez - embora estar ciente desse comportamento ainda seja bom.


Há uma diferença importante entre usar funções em vez de classes, ou seja: O framework não tem conhecimento das funções, mas pode ver as classes.

Considere a seguinte função de "widget":

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

usado desta forma:

functionWidget(
  child: functionWidget(),
);

E é equivalente a 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,
    );
  }
}

usado assim:

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

No papel, ambos parecem fazer exatamente a mesma coisa: criar 2 Container, com um aninhado no outro. Mas a realidade é um pouco diferente.

No caso de funções, a árvore de widgets gerada é semelhante a esta:

Container
  Container

Enquanto com classes, a árvore do widget é:

ClassWidget
  Container
    ClassWidget
      Container

Isso é importante porque muda como a estrutura se comporta ao atualizar um widget.

Por que isso importa

Ao usar funções para dividir sua árvore de widgets em vários widgets, você se expõe a bugs e perde algumas otimizações de desempenho.

Não há garantia de que você terá bugs usando funções, mas usando classes, você tem a garantia de não enfrentar esses problemas.

Aqui estão alguns exemplos interativos no Dartpad que você pode executar para entender melhor os problemas:

Conclusão

Aqui está uma lista selecionada das diferenças entre o uso de funções e classes:

  1. Aulas:
  • permite a otimização do desempenho (construtor const, reconstrução mais granular)
  • certifique-se de que alternar entre dois layouts diferentes descarte corretamente os recursos (as funções podem reutilizar algum estado anterior)
  • garante que o hot-reload funcione corretamente (o uso de funções pode interromper o hot-reload para showDialogs& semelhantes)
  • são integrados ao inspetor de widgets.
    • Vemos ClassWidgetna árvore de widgets mostrada pelo devtool, que ajuda a entender o que está na tela
    • Podemos substituir debugFillProperties para imprimir quais são os parâmetros passados ​​para um widget
  • melhores mensagens de erro
    Se ocorrer uma exceção (como ProviderNotFound), a estrutura fornecerá o nome do widget atualmente em construção. Se você dividiu sua árvore de widgets apenas em funções + Builder, seus erros não terão um nome útil
  • pode definir chaves
  • pode usar a API de contexto
  1. Funções:
  • têm menos código (o que pode ser resolvido usando a geração de código funcional_widget )

No geral, é considerado uma prática ruim usar funções em vez de classes para reutilizar widgets por esses motivos.
Você pode , mas pode morder você no futuro.

Rémi Rousselet
fonte
Os comentários não são para discussão extensa; esta conversa foi movida para o chat .
Samuel Liew
10

Tenho pesquisado sobre esse problema nos últimos 2 dias. Cheguei à seguinte conclusão: tudo bem dividir partes do aplicativo em funções. É ideal que essas funções retornem um StatelessWidget, então otimizações podem ser feitas, como fazer o StatelessWidget const, para que ele não reconstrua se não for necessário. Por exemplo, este trecho de código é perfeitamente 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"),
    );
  }
}

O uso de função ali está perfeitamente bem, pois retorna a const StatelessWidget. Por favor me corrija se eu estiver errado.

Sergiu Iacob
fonte
Alguém pode explicar por que o que eu disse está errado? Quer dizer, acho que é errado, considerando os votos negativos.
Sergiu Iacob
Na verdade, eu concordo com você. Pretendo escrever uma análise muito mais detalhada das diferenças, mas ainda não dei tempo para isso. Sinta-se à vontade para desenvolver seu argumento, pois acho importante entender os prós e os contras dos métodos de widgets v.
TheIT
@SergiuIacob Podemos usar constna frente da classe apátrida para todos os casos? Ou tem que ser certos casos? Se sim, quais são eles?
aytunch de
1
@aytunch Acho que você não pode usar em consttodos os lugares. Por exemplo, se você tem uma StatelessWidgetclasse que retorna um Textcontendo o valor de uma variável, e essa variável muda em algum lugar, então você StatelessWidgetdeve ser reconstruído, para que possa mostrar aquele valor diferente, portanto, não pode ser const. Acho que a forma segura de colocar é a seguinte: onde puder, use const, se for seguro fazê-lo.
Sergiu Iacob
3
Estou debatendo se devo responder a essa pergunta sozinho. A resposta aceita está completamente errada, mas Rémi tem feito muito para tentar ajudar a comunidade agitada, então as pessoas provavelmente não examinam suas respostas tanto quanto as dos outros. Isso pode ficar evidente por todos os votos positivos. As pessoas querem apenas sua "única fonte de verdade". :-)
DarkNeuron
4

Havia uma grande diferença entre o que as funções fazem e o que a classe faz.


Vamos explicar do zero.🙂 (apenas sobre o imperativo)

  • Todos nós sabemos que a história da programação começou com comandos básicos diretos (por exemplo: Montagem).

  • Em seguida, a programação estruturada veio com controles de fluxo (por exemplo: if, switch, while, for etc). Este paradigma permite aos programadores controlar o fluxo do programa de forma eficaz e também minimizar o número de linhas de código por loops.

  • Em seguida, veio a programação de procedimentos, que agrupa as instruções em procedimentos (funções). Isso deu dois grandes benefícios para os programadores.

    1. Agrupe as instruções (operações) em blocos separados.

    2. É possível reutilizar esses blocos. (Funções)

Mas, acima de tudo, os paradigmas não deram uma solução para o gerenciamento de aplicativos. A programação de procedimentos também pode ser usada apenas para aplicativos de pequena escala. Isso não pode ser usado para desenvolver grandes aplicativos da web (por exemplo: banking, google, youtube, facebook, stackoverflow etc), não pode criar estruturas como android sdk, flutter sdk e muito mais ......

Assim, os engenheiros pesquisam muito mais para gerenciar os programas de maneira adequada.

  • Finalmente, a Programação Orientada a Objetos vem com todas as soluções para gerenciar aplicativos em qualquer escala (de hello world a Trilhões de pessoas usando a criação de sistemas, por exemplo, google, amazon e hoje 90% dos aplicativos).

  • Em oop, todos os aplicativos são construídos em torno de Objetos. Significa que o aplicativo é uma coleção desses objetos.

portanto, os objetos são a construção básica para qualquer aplicativo.

classe (objeto em tempo de execução) agrupa dados e funções relacionadas a essas variáveis ​​(dados). assim, o objeto compõe os dados e suas operações relacionadas.

[Aqui, não vou explicar sobre oop]


👉👉👉 Ok, agora vamos para a estrutura de flutter.👈👈👈

-Dart suporta procedimentos e oop Mas, o framework Flutter é completamente construído usando classes (oop). (Porque grande estrutura gerenciável não pode criar usando procedural)

Aqui, vou criar uma lista de razões pelas quais eles usam classes em vez de funções para fazer widgets.👇👇👇


1 - Na maioria das vezes, o método de construção (widget filho) chama o número de funções síncronas e assíncronas.

Ex:

  • Para baixar a imagem da rede
  • obter entrada do usuário etc.

portanto, o método de construção precisa ser mantido em um widget de classe separado (porque todos os outros métodos chamados pelo método build () podem ser mantidos em uma classe)


2 - Usando a classe widget, você pode criar o número de outra classe sem escrever o mesmo código repetidas vezes (** Use Of Inheritance ** (extends)).

E também usando herança (estender) e polimorfismo (substituir), você pode criar sua própria classe personalizada. (Abaixo o exemplo, lá eu irei personalizar (Substituir) a animação estendendo MaterialPageRoute (porque sua transição padrão eu quero personalizar) .👇

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 - Funções não podem adicionar condições para seus parâmetros, mas usando o construtor do widget de classe, você pode fazer isso.

Abaixo, exemplo de código👇 (este recurso é muito usado por widgets de 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 - As funções não podem usar const e o widget Class pode usar const para seus construtores. (que afetam o desempenho do tópico principal)


5 - Você pode criar qualquer número de widgets independentes usando a mesma classe (instâncias de uma classe / objetos). Mas a função não pode criar widgets independentes (instância), mas a reutilização pode.

[cada instância tem sua própria variável de instância e que é completamente independente de outros widgets (objeto), mas a variável local da função depende de cada chamada de função * (o que significa que, quando você altera um valor de uma variável local, ela afeta todas as outras partes do o aplicativo que usa esta função)]


Havia muitas vantagens na aula em relação às funções. (Acima, apenas alguns casos de uso)


🤯 Meu pensamento final

Portanto, não use funções como bloco de construção de seu aplicativo, use-as apenas para fazer operações. Caso contrário, ele causará muitos problemas impossíveis de lidar quando seu aplicativo se tornar escalável .

  • Use funções para fazer uma pequena parte da tarefa
  • Use a classe como bloco de construção de um aplicativo (gerenciamento de aplicativos)

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

VOCÊ NÃO PODE MEDIR A QUALIDADE DO PROGRAMA POR NÚMERO DE DECLARAÇÕES (ou linhas) USO POR ELE

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

Obrigado pela leitura

TDM
fonte
Bem-vindo ao Stackoverflow! Não tenho certeza do que você está tentando expressar com sua resposta. Você pode usar uma função perfeitamente para construir widgets. shrinkHelper() { return const SizedBox.shrink(); }é o mesmo que usar const SizedBox.shrink()embutido em sua árvore de widgets e, usando funções auxiliares, você pode limitar a quantidade de aninhamento em um só lugar.
DarkNeuron
@DarkNeuron Obrigado por compartilhar. Vou tentar usar funções auxiliares.
TDM
2

Ao chamar o widget Flutter, certifique-se de usar a palavra-chave const. Por exemploconst MyListWidget();

user4761410
fonte
9
Posso saber como isso responde à pergunta do OP?
CopsOnRoad
2
Parece que respondi na seção errada. Eu estava tentando responder à pergunta de Daniel de que o método de construção de widget sem estado refatorado ainda está sendo chamado. Ao adicionar a constpalavra - chave ao chamar o widget sem estado refatorado, ele deve ser chamado apenas uma vez.
user4761410
1
Está bem. Entendi. As pessoas podem votar contra esta resposta, pois não tem nada a ver com a pergunta OP. Portanto, você deve excluí-lo. De qualquer forma, a escolha é sua.
CopsOnRoad