我已经意识到可以使用普通函数创建小部件而不是子类化StatelessWidget.一个例子是这样的:
Widget function({ String title, VoidCallback callback }) {
return GestureDetector(
onTap: callback,
child: // some widget
);
}
Run Code Online (Sandbox Code Playgroud)
这很有趣,因为它需要的代码远远少于完整的类.例:
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
);
}
}
Run Code Online (Sandbox Code Playgroud)
所以我一直在想:除了创建小部件的函数和类之间的语法之外还有什么区别吗?使用函数是一个好习惯吗?
Rém*_*let 96
TL; DR:永远不要使用类上的函数来制作可重用的小部件树.始终将这些提取到StatelessWidget中.
使用函数而不是类之间存在巨大差异,即:框架不知道函数,但可以看到类.
考虑以下"小部件"功能:
Widget functionWidget({ Widget child}) {
return Container(child: child);
}
Run Code Online (Sandbox Code Playgroud)
用这种方式:
functionWidget(
child: functionWidget(),
);
Run Code Online (Sandbox Code Playgroud)
它等同于类:
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,
);
}
}
Run Code Online (Sandbox Code Playgroud)
像那样使用:
new ClassWidget(
child: new ClassWidget(),
);
Run Code Online (Sandbox Code Playgroud)
在纸面上,两者似乎完全相同:创建2 Container
,其中一个嵌套到另一个中.但现实略有不同.
在函数的情况下,生成的小部件树如下所示:
Container
Container
Run Code Online (Sandbox Code Playgroud)
使用类时,窗口小部件树是:
ClassWidget
Container
ClassWidget
Container
Run Code Online (Sandbox Code Playgroud)
这非常重要,因为它从根本上改变了更新窗口小部件时框架的行为方式.以下是差异的精选列表:
类别:
功能:
结论应该很清楚:
不要使用函数来创建小部件.
Gaz*_*kus 17
正如 Remi反复雄辩地指出的那样,导致问题的不是函数本身,问题是我们认为使用函数与使用新小部件具有类似的好处。
不幸的是,这个建议正在演变成“仅仅使用函数的行为是低效的”,并且经常错误地推测其原因。
使用函数几乎与使用函数返回值代替该函数相同。因此,如果您调用一个小部件构造函数并将其作为另一个小部件的子项,那么将该构造函数调用移至函数中并不会导致代码效率低下。
//...
child: SomeWidget(),
//...
Run Code Online (Sandbox Code Playgroud)
就效率而言并没有明显优于
//...
child: buildSomeWidget();
//...
Widget buildSomeWidget() => SomeWidget();
Run Code Online (Sandbox Code Playgroud)
关于第二个问题可以争论以下几点:
AnimatedSwitcher
et al 一起使用。Scaffold
通过上下文到达它上面ChangeNotifier
它,它的重建不包含在函数中但这样的争论是不正确的:
创建新的小部件会带来以下性能优势:
ChangeNotifier
在它内部不会使其父级在更改时重建const
(如果可能的话)可以保护它免受父级重建的影响const
如果您可以将不断变化的子部件与其他小部件隔离,那么您更有可能保留构造函数但是,如果您没有任何这些情况,并且您的构建函数看起来越来越像金字塔的厄运,那么最好将其一部分重构为函数,而不是保留金字塔。特别是如果您强制执行 80 个字符的限制,您可能会发现自己在大约 20 个字符宽的空间中编写代码。我看到很多新手都陷入了这个陷阱。给这些新手的信息应该是“你真的应该在这里创建新的小部件。但如果你不能,至少创建一个函数。”,而不是“你必须创建一个小部件,否则!”。这就是为什么我认为我们在推广小部件而不是功能时必须更加具体,并避免在效率方面出现事实上的错误。
为了您的方便,我重构了Remi 的代码,以表明问题不仅仅是使用函数,而是避免创建新的小部件。因此,如果您要将这些函数中的小部件创建代码放入调用函数的位置(重构内联),您将获得与使用函数完全相同的行为,但不使用函数!因此,问题不在于使用函数,而在于避免创建新的小部件类。
(记得关闭空安全,因为原始代码是 2018 年的)
以下是 Dartpad 上的一些交互式示例,您可以自行运行以更好地理解问题:
https://dartpad.dev/1870e726d7e04699bc8f9d78ba71da35此示例展示了如何通过将应用程序拆分为函数,您可能会意外地破坏 AnimatedSwitcher 等内容
非功能版本:https://dartpad.dev/? id=ae5686f3f760e7a37b682039f546a784
https://dartpad.dev/a869b21a2ebd2466b876a5997c9cf3f1此示例展示了类如何允许更精细地重建小部件树,从而提高性能
非功能版本:https://dartpad.dev/? id=795f286791110e3abc1900e4dcd9150b
https://dartpad.dev/06842ae9e4b82fad917acb88da108eee此示例展示了如何通过使用函数,使自己在使用 InheritedWidget(例如主题或提供程序)时面临滥用 BuildContext 和面临错误的风险
非功能版本:https://dartpad.dev/? id=65f753b633f68503262d5adc22ea27c0
您会发现,如果函数中没有它们,则会产生完全相同的行为。因此,添加小部件可以为您带来胜利。添加功能不会产生问题。
所以建议应该是:
setState
等)。然后提取你的小部件,并找到添加这些东西的方法。将函数传递给构造函数可能没问题(想想 onPressed)。使用状态管理系统可能会更好。我希望这可以帮助提醒我们为什么我们更喜欢小部件而不是函数,并且简单地使用函数并不是一个大问题。
编辑:整个讨论中遗漏了一点:当您进行小部件化时,兄弟姐妹不再相互重建。此 Dartpad 演示了这一点:https://dartpad.dartlang.org/? id=8d9b6d5bd53a23b441c117cd95524892
小智 16
过去两天我一直在研究这个问题。我得出以下结论:将应用程序的各个部分分解为功能是可以的。这些函数返回 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"),
);
}
}
Run Code Online (Sandbox Code Playgroud)
在那里使用函数非常好,因为它返回一个const StatelessWidget
. 如果我错了,请纠正我。
函数的作用和类的作用之间存在很大差异。
让我从头开始解释它。(仅关于命令式)
编程历史,我们都知道从直接的基本命令开始(例如-:Assembly)。
Next 结构化编程带有流程控制(例如:if、switch、while、for 等)这种范式使程序员可以有效地控制程序流程,并通过循环最大限度地减少代码行数。
下一个过程编程出现了,它将指令分组到过程(函数)中。这给程序员带来了两大好处。
1.Group 语句(操作)到单独的块中。
2.可以重用这些块。(功能)
但最重要的是,范式并没有给出管理应用程序的解决方案。过程式编程也只能用于小规模的应用程序。不能用于开发大型 Web 应用程序(例如:银行、谷歌、youtube、facebook、stackoverflow 等),不能创建像 android sdk、flutter sdk 等框架......
所以工程师们做了更多的研究来正确地管理程序。
最后,面向对象编程提供了管理任何规模应用程序的所有解决方案。(从 hello world 到使用系统创建的万亿人,例如谷歌、亚马逊,以及今天 90% 的应用程序)。
在oop中,所有应用程序都是围绕对象构建的。这意味着应用程序是这些对象的集合。
所以对象是任何应用程序的基本构建。
类(运行时的对象)对与这些变量(数据)相关的数据和函数进行分组。所以对象组合数据及其相关操作。
[这里我不打算解释 oop ]
好的现在让我们来看看颤振框架。
-Dart 支持过程式和 oop 但是,Flutter 框架完全使用类(oop)构建。(因为大型可管理框架无法使用程序创建)
在这里,我将创建他们使用类而不是函数来制作小部件的原因列表。
1 - 大多数时候构建方法(子小部件)调用同步和异步函数的数量。
前任:
所以 build 方法需要保存在单独的类小部件中(因为 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);
}
}
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
4 - 函数不能使用 const,而类小部件可以使用 const 作为其构造函数。(影响主线程的性能)
5 - 您可以使用相同的类(类/对象的实例)创建任意数量的独立小部件,但函数不能创建独立的小部件(实例),但可以重用。
[每个实例都有自己的实例变量,并且完全独立于其他小部件(对象),但是函数的局部变量取决于每个函数调用*(这意味着,当您更改局部变量的值时,它会影响所有其他部分使用此功能的应用程序)]
类比函数有很多优点..(以上仅是少数用例)
我最后的想法
因此,不要将函数用作应用程序的构建块,而仅将它们用于执行操作。否则,当您的应用程序变得可扩展时,它会导致许多无法处理的问题。
谢谢阅读
归档时间: |
|
查看次数: |
5773 次 |
最近记录: |