В чем разница между функциями и классами для создания виджетов?
Я понял, что можно создавать виджеты, используя простые функции вместо создания подклассов 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
);
}
}
Итак, мне было интересно: есть ли разница, кроме синтаксиса между функциями и классами для создания виджетов? И это хорошая практика для использования функций?
Ответы
Ответ 1
TL; DR: никогда не используйте функции над классами для создания многократно используемого дерева виджетов. Вместо этого всегда извлекайте их в 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
Это очень важно, потому что это радикально меняет поведение платформы при обновлении виджета. Вот список курируемых отличий:
-
Классы:
- разрешить оптимизацию производительности (конструктор const, оператор == переопределение, более детальная перестройка)
- иметь горячую перезагрузку
- интегрированы в инспектор виджетов (debugFillProperties)
- можно определить ключи
- можно использовать контекстный API
- убедитесь, что все виджеты используются одинаково (всегда конструктор)
- убедитесь, что переключение между двумя различными макетами корректно избавляет от ресурсов (функции могут повторно использовать некоторое предыдущее состояние)
-
Функции:
- у меня меньше кода (и даже там, я сделал генератор кода, чтобы сделать классы такими же маленькими, как функции: functions_widget)
- ?
Вывод должен быть уже достаточно ясным:
Не используйте функции для создания виджетов.
Ответ 2
Когда вы вызываете виджет Flutter, убедитесь, что вы используете ключевое слово const. Например, const MyListWidget();
Ответ 3
Я изучал эту проблему в течение последних 2 дней. Я пришел к следующему выводу: можно разбивать части приложения на функции. Это просто идеально, что эти функции возвращают 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
. Пожалуйста, поправьте меня, если я ошибаюсь.
Ответ 4
Полезная статья, которая дает обзор/обоснование для всегда разделения на классы, а не методы:
https://iirokrankka.com/2018/12/11/splitting-widgets-to-methods-performance-antipattern/