Чим відрізняються функції та класи для створення віджетів для багаторазового використання?


125

Я зрозумів, що можна створювати віджети, використовуючи прості функції замість підкласингу 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

Відповіді:


172

TL; DR: Віддайте перевагу використанню класів над функціями для створення дерева віджетів для багаторазового використання .


EDIT : Щоб компенсувати непорозуміння: мова йде не про функції, що викликають проблеми, а класи, які вирішують деякі.

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 (таких як Тема чи провайдери)

Висновок

Ось наведений список відмінностей між використанням функцій та класів:

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

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


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Самуель Liew

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

Використання функції там прекрасно, оскільки вона повертає a const StatelessWidget. Будь ласка, виправте мене, якщо я помиляюся.


Хтось може пояснити, чому те, що я сказав, є неправильним? Я маю на увазі, я гадаю, що це неправильно, враховуючи сутіни
Серджіу Якоб

Я фактично з вами згоден. Я мав намір написати набагато детальнішу розбіжність відмінностей, але до цього не дійшов. Не соромтеся викладати свої аргументи, тому що я думаю, що важливо зрозуміти плюси і мінуси методів віджетів v.
THEIT

@SergiuIacob Чи можемо ми використати constперед класом без громадянства для кожного випадку? Або це повинні бути певні випадки? Якщо так, то що вони?
другий день

1
@aytunch Я не думаю, що ти можеш використовувати constвсюди. Наприклад, якщо у вас є StatelessWidgetклас, який повертає Textмістити значення змінної, і ця змінна десь змінюється, ніж ваша StatelessWidgetповинна бути перебудована, щоб вона могла показувати інше значення, тому не може бути const. Я думаю, що найбезпечнішим способом є це: де можна, використовуйте const, якщо це безпечно.
Сергій Якоб

3
Я дискутував, чи відповідати на це питання сам. Прийнята відповідь є явно неправильною, але Ремі зробив багато, щоб спробувати допомогти спільноті, що хитається, тому люди, ймовірно, не вивчають його відповідей так сильно, як чиїсь. Це може бути видно з усіх результатів. Люди просто хочуть свого "єдиного джерела істини". :-)
DarkNeuron

4

Існувала велика різниця між тим, що виконує функції, і тим, що робить клас.


Давайте я поясню це з дуже нуля.🙂 (лише про імператив)

  • Історія програмування, всі ми знаємо, почалася з прямих базових команд (наприклад-: Збірка).

  • Наступне Структуроване програмування поставлено з елементами управління потоками (наприклад, якщо:, переключення, в той час як для і т. Д.) Ця парадигма дає програмістам можливість ефективно контролювати потік програми, а також її мінімізувати кількість рядків коду за допомогою циклів.

  • Далі з'явилося програмування процедур, яке об'єднує вказівки щодо процедур (функцій). Це дало дві основні переваги програмістам.

    1. Групові оператори (операції) в окремі блоки.

    2.Можна використовувати ці блоки. (Функції)

Але перш за все парадигми не давали рішення для керування програмами. Процедурне програмування також можна використовувати лише для невеликих програм. Це не можна використовувати для розробки великих веб-додатків (наприклад,: банківська справа, google, youtube, facebook, stackoverflow тощо), не може створювати рамки, такі як android sdk, flutter sdk та багато іншого ......

Тож інженери роблять набагато більше досліджень для правильного управління програмами.

  • Нарешті, об'єктно-орієнтоване програмування має все рішення для управління додатками будь-якого масштабу (від привітного світу до трильйону людей, що використовують створення системи, наприклад, google, amazon та сьогодні 90% додатків).

  • У oop всі програми створюються навколо об'єктів. Це означає, що додаток - це сукупність цих об'єктів.

тому об'єкти є основною спорудою для будь-якого застосування.

клас (об’єкт під час виконання) групових даних та функцій, пов'язаних із цими змінними (даними). тому об'єкт складається з даних та пов'язаних з ними операцій.

[Тут я не збираюся пояснювати про oop]


Ок зараз Давайте прийдемо для розмивання рамки.👈👈👈

-Dart підтримує як процедурні, так і oop Але, Flutter Framework повністю будується за допомогою класів (oop). (Оскільки великий керований фреймворк неможливо створити, використовуючи процедурні)

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


1 - У більшості випадків метод побудови (довідкового віджета) викликає кількість синхронних та асинхронних функцій.

Наприклад:

  • Завантажити зображення в мережі
  • отримати вхід від користувача тощо.

тому метод збирання потрібно зберігати в окремому віджеті класу (тому що всі інші методи виклику методом build () можуть зберігатися в одному класі)


2 - Використовуючи клас віджетів, ви можете створювати номер іншого класу, не записуючи той самий код знову і знову (** Використання спадкування ** (розширення)).

А також, використовуючи успадкування (розширення) та поліморфізм (переопределення), ви можете створити власний власний клас. (Нижче прикладу нижче, там я налаштувати (переосмислити) анімацію, розширивши 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 - Функції не можуть використовувати const, а віджет Class може використовувати const для своїх конструкторів. (які впливають на продуктивність основної нитки)


5 - Ви можете створити будь-яку кількість незалежних віджетів за допомогою одного класу (екземпляри класу / об’єктів).

[кожен екземпляр має власну змінну екземпляра і повністю незалежний від інших віджетів (об'єкта), але локальна змінна функції функції залежить від кожного виклику функції * (це означає, що при зміні значення локальної змінної це впливає на всі інші частини програма, яка використовує цю функцію)]


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


🤯 Моя заключна думка

Тому не використовуйте Функції як складовий блок програми, використовуйте їх лише для виконання операцій. Інакше це може спричинити багато незмінних проблем, коли ваша програма стає масштабованою .

  • Використовуйте функції для виконання невеликої частини завдань
  • Використовувати клас як будівельний блок програми (Керування додатком)

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

НЕ МОЖЕТЕ ВИМІРИТИ ЯКІСТЬ ПРОГРАМИ НА РОЗМІРУВАННЯ (або рядків), ВИКОРИСТОВУВАННЯ нею

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

Дякуємо за прочитане


Ласкаво просимо до Stackoverflow! Я не дуже впевнений, що ви намагаєтеся висловити своєю відповіддю. Ви можете використовувати функцію просто для створення віджетів. shrinkHelper() { return const SizedBox.shrink(); }це те саме, що використовувати const SizedBox.shrink()вбудоване дерево у вашому дереві віджетів, і за допомогою допоміжних функцій ви можете обмежити кількість вкладених в одне місце.
DarkNeuron

@DarkNeuron Дякуємо за обмін. Я спробую використовувати допоміжні функції.
TDM

2

Коли ви телефонуєте віджет Flutter, переконайтеся, що ви використовуєте ключове слово const. Наприкладconst MyListWidget();


9
Чи можу я знати, як це відповідає на питання ОП?
CopsOnRoad

2
Подивіться, я відповів, що я неправильний розділ. Я намагався відповісти на запитання Даніеля про те, що відремонтований метод побудови віджетів без громадянства досі називається. Додавши constключове слово під час виклику відреставрованого віджета без стану, його слід викликати лише один раз.
user4761410

1
Гаразд. Зрозумів. Люди можуть відповісти на цю відповідь, оскільки це не має нічого спільного з питанням про ОП. Тому слід видалити його. У будь-якому випадку вибір за вами.
CopsOnRoad
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.