В чем разница между функциями и классами для создания многоразовых виджетов?

126

Я понял, что можно создавать виджеты с использованием простых функций вместо создания подкласса StatelessWidget . Примером может быть такой:

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

Это интересно, потому что для этого требуется гораздо меньше кода, чем для полноценного класса. Пример:

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

Поэтому мне было интересно: есть ли какая-нибудь разница, помимо синтаксиса, между функциями и классами для создания виджетов? И полезно ли использовать функции?

Реми Русселе
источник
Я нашел эту ветку очень полезной для понимания проблемы. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Ответы:

174

TL; DR: предпочитайте использовать классы вместо функций для создания многоразового дерева виджетов.


РЕДАКТИРОВАТЬ : Чтобы исправить некоторое недоразумение: речь идет не о функциях, вызывающих проблемы, а о классах, решающих некоторые.

У Flutter не было бы StatelessWidget, если бы функция могла делать то же самое.

Точно так же он в основном предназначен для общедоступных виджетов, предназначенных для повторного использования. Не имеет большого значения, что частные функции предназначены для использования только один раз - хотя знать об этом поведении все равно хорошо.


Существует важное различие между использованием функций вместо классов, а именно: фреймворк не знает о функциях, но может видеть классы.

Рассмотрим следующую функцию «виджета»:

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

использовал этот способ:

functionWidget(
  child: functionWidget(),
);

И это эквивалент класса:

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

используется так:

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

На бумаге кажется, что оба делают одно и то же: создайте 2 Container, причем одно вложено в другое. Но на самом деле все обстоит иначе.

В случае функций сгенерированное дерево виджетов выглядит так:

Container
  Container

Дерево виджетов с классами выглядит следующим образом:

ClassWidget
  Container
    ClassWidget
      Container

Это важно, потому что это меняет поведение фреймворка при обновлении виджета.

Почему это важно

Используя функции для разделения дерева виджетов на несколько виджетов, вы подвергаетесь ошибкам и пропускаете некоторые оптимизации производительности.

Нет гарантии, что у вас будут ошибки при использовании функций, но при использовании классов вы гарантированно не столкнетесь с этими проблемами.

Вот несколько интерактивных примеров на Dartpad, которые вы можете запустить самостоятельно, чтобы лучше понять проблемы:

  • https://dartpad.dev/1870e726d7e04699bc8f9d78ba71da35
    Этот пример демонстрирует, как, разделив приложение на функции, вы можете случайно сломать такие вещи, какAnimatedSwitcher

  • https://dartpad.dev/a869b21a2ebd2466b876a5997c9cf3f1 В
    этом примере показано, как классы позволяют более детально перестраивать дерево виджетов, повышая производительность.

  • https://dartpad.dev/06842ae9e4b82fad917acb88da108eee
    Этот пример демонстрирует, как, используя функции, вы подвергаете себя злоупотреблению BuildContext и сталкиваетесь с ошибками при использовании InheritedWidgets (например, Theme или провайдеров).

Вывод

Вот тщательно подобранный список различий между использованием функций и классов:

  1. Классы:
  • разрешить оптимизацию производительности (конструктор const, более детальная перестройка)
  • убедитесь, что переключение между двумя разными макетами правильно избавляет от ресурсов (функции могут повторно использовать некоторое предыдущее состояние)
  • гарантирует, что горячая перезагрузка работает должным образом (использование функций может нарушить горячую перезагрузку showDialogsи т.п.)
  • интегрированы в инспектор виджетов.
    • Мы видим ClassWidgetв дереве виджетов, которое показывает инструмент разработчика, что помогает понять, что находится на экране.
    • Мы можем переопределить debugFillProperties, чтобы распечатать параметры, переданные виджету.
  • улучшенные сообщения об ошибках.
    Если происходит исключение (например, ProviderNotFound), фреймворк выдаст вам имя создаваемого в данный момент виджета. Если вы разделили дерево виджетов только на functions + Builder, у ваших ошибок не будет удобного названия.
  • может определять ключи
  • может использовать контекстный API
  1. Функции:
  • иметь меньше кода (что может быть решено с помощью создания кода function_widget )

В целом, по этим причинам использование функций вместо классов для повторного использования виджетов считается плохой практикой.
Вы можете , но она может укусить вас в будущем.

Реми Русселе
источник
Комментарии не подлежат расширенному обсуждению; этот разговор был перемещен в чат .
Сэмюэл Лью
10

Я занимаюсь этим вопросом последние 2 дня. Я пришел к следующему выводу: НУЖНО разбивать части приложения на функции. Просто идеально, чтобы эти функции возвращали a StatelessWidget, чтобы можно было сделать оптимизацию, например, сделать StatelessWidget const, чтобы он не перестраивался, если в этом нет необходимости. Например, этот фрагмент кода совершенно верен:

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

Использование функции здесь совершенно нормально, поскольку она возвращает const StatelessWidget. Пожалуйста, поправьте меня, если я ошибаюсь.

Серджиу Якоб
источник
Может кто-нибудь объяснить, почему то, что я сказал, неправильно? Я имею в виду, я полагаю, что это неправильно, учитывая отрицательные голоса.
Серджиу Якоб
Я действительно согласен с тобой. Я намеревался написать более подробное описание различий, но пока не дошел до этого. Не стесняйтесь конкретизировать свои аргументы, поскольку я считаю важным понимать плюсы и минусы методов vidgets.
TheIT
@SergiuIacob Можно ли использовать constперед классом без сохранения состояния в каждом случае? Или это должны быть определенные случаи? Если да, то какие?
айтунч
1
@aytunch Не думаю, что можно использовать constвезде. Например, если у вас есть StatelessWidgetкласс, который возвращает, Textсодержащий значение переменной, и эта переменная где-то изменяется, тогда ваш StatelessWidgetдолжен быть перестроен, чтобы он мог отображать это другое значение, поэтому это не может быть const. Я думаю, что безопасный способ сформулировать это так: где можно, используйте const, если это безопасно.
Серджиу Якоб
3
Я долго думал, стоит ли самому отвечать на этот вопрос. Принятый ответ совершенно неверен, но Реми много сделал, чтобы попытаться помочь сообществу флаттеров, поэтому люди, вероятно, не так внимательно изучают его ответы, как чужие. Это может быть видно из всех голосов "за". Люди просто хотят иметь свой «единственный источник правды». :-)
DarkNeuron
4

Была большая разница между тем, что делают функции и что делает класс.


Позвольте мне объяснить это с нуля. (Только про императив)

  • Все мы знаем, что история программирования началась с простых базовых команд (например, «Сборка»).

  • Далее Структурированное программирование пришло с элементами управления потоком (например: if, switch, while, for и т. Д.). Эта парадигма дает программистам возможность эффективно управлять потоком программы, а также минимизировать количество строк кода за циклами.

  • Далее пришло процедурное программирование, которое группирует инструкции в процедуры (функции). Это дало программистам два основных преимущества.

    1. Сгруппируйте операторы (операции) в отдельные блоки.

    2.Можно повторно использовать эти блоки. (Функции)

Но прежде всего парадигмы не дали решения для Управления приложениями. Процедурное программирование также можно использовать только для небольших приложений. Это нельзя использовать для разработки больших веб-приложений (например: банковское дело, google, youtube, facebook, stackoverflow и т. Д.), Нельзя создавать фреймворки, такие как android sdk, flutter sdk и многое другое ...

Поэтому инженеры проводят гораздо больше исследований, чтобы правильно управлять программами.

  • Наконец, объектно-ориентированное программирование поставляется со всеми решениями для управления приложениями любого масштаба (от привет, мир до триллиона людей, использующих создание системы, например, google, amazon, и сегодня 90% приложений).

  • В общем, все приложения построены на объектах. Это означает, что приложение представляет собой набор этих объектов.

Таким образом, объекты - это основа любого приложения.

класс (объект во время выполнения) группирует данные и функции, связанные с этими переменными (данными). поэтому объект состоит из данных и связанных с ними операций.

[Здесь я не буду объяснять о уп]


«Хорошо, теперь перейдем к фреймворку флаттера».

-Dart поддерживает как процедурные, так и oop. Но фреймворк Flutter полностью построен с использованием классов (oop). (Потому что большая управляемая структура не может быть создана с использованием процедурных)

Здесь я создам список причин, по которым они используют классы вместо функций для создания виджетов.


1 - Чаще всего метод сборки (дочерний виджет) вызывает ряд синхронных и асинхронных функций.

Пример:

  • Скачать сетевой образ
  • получить ввод от пользователя и т. д.

поэтому метод сборки необходимо хранить в отдельном виджете класса (потому что все другие методы, вызываемые методом build (), могут храниться в одном классе)


2 - Используя класс виджета, вы можете создать номер другого класса, не записывая один и тот же код снова и снова (** Use Of Inheritance ** (extends)).

А также используя наследование (расширение) и полиморфизм (переопределение), вы можете создать собственный собственный класс. (Пример ниже: там я настрою (переопределю) анимацию, расширив MaterialPageRoute (потому что его переход по умолчанию, который я хочу настроить).

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 - Функции не могут добавлять условия для своих параметров, но с помощью конструктора виджета класса Вы можете это сделать.

Ниже пример кода👇 (эта функция активно используется виджетами фреймворка)

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 - Функции не могут использовать константу, а виджет класса может использовать константу для своих конструкторов. (которые влияют на производительность основного потока)


5 - Вы можете создать любое количество независимых виджетов, используя один и тот же класс (экземпляры класса / объектов). Но функция не может создавать независимые виджеты (экземпляр), а повторное использование может.

[у каждого экземпляра есть своя собственная переменная экземпляра, которая полностью независима от других виджетов (объекта), но локальная переменная функции зависит от каждого вызова функции * (что означает, что когда вы меняете значение локальной переменной, это влияет на все другие части приложение, которое использует эту функцию)]


У класса было много преимуществ над функциями ... (выше приведены только несколько вариантов использования)


🤯 Моя последняя мысль

Поэтому не используйте функции как строительный блок вашего приложения, используйте их только для выполнения операций. В противном случае это вызовет множество неуправляемых проблем, когда ваше приложение станет масштабируемым .

  • Используйте функции для выполнения небольшой части задачи
  • Использовать класс как строительный блок приложения (Управление приложением)

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

ВЫ НЕ МОЖЕТЕ ИЗМЕРЯТЬ КАЧЕСТВО ПРОГРАММЫ ПО КОЛИЧЕСТВУ ЗАЯВЛЕНИЙ (или строк), ИСПОЛЬЗУЕМЫХ ею

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

Спасибо за прочтение

TDM
источник
Добро пожаловать в Stackoverflow! Я не совсем понимаю, что вы пытаетесь выразить своим ответом. Вы можете использовать функцию просто отлично для создания виджетов. shrinkHelper() { return const SizedBox.shrink(); }это то же самое, что использование const SizedBox.shrink()встроенного в дереве виджетов, а с помощью вспомогательных функций вы можете ограничить количество вложений в одном месте.
DarkNeuron
@DarkNeuron Спасибо, что поделились. Я попробую использовать вспомогательные функции.
TDM
2

Когда вы вызываете виджет Flutter, убедитесь, что вы используете ключевое слово const. Напримерconst MyListWidget();

user4761410
источник
9
Могу я узнать, как это отвечает на вопрос ОП?
CopsOnRoad
2
Похоже, я ответил не в том разделе. Я пытался ответить на вопрос Дэниела о том, что отремонтированный метод сборки виджета без сохранения состояния все еще вызывается. При добавлении constключевого слова при вызове рефакторинга виджета без сохранения состояния его следует вызывать только один раз.
user4761410
1
Хорошо. Понял. Люди могут проголосовать против этого ответа, поскольку он не имеет ничего общего с вопросом OP. Так что вы должны удалить его. В любом случае выбор за вами.
CopsOnRoad