创建可重用窗口小部件的函数和类之间有什么区别?


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

Answers:


171

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上的一些交互式示例,您可以运行这些示例来更好地理解问题:

结论

以下是精选的使用函数和类之间的区别的列表:

  1. 类:
  • 允许性能优化(const构造函数,更精细的重建)
  • 确保在两个不同的布局之间切换可以正确处理资源(功能可能会重用某些先前的状态)
  • 确保热重装正常(使用功能可能会中断热重装等showDialogs
  • 已集成到小部件检查器中。
    • 我们ClassWidget在devtool显示的小部件树中看到了它,这有助于了解屏幕上的内容
    • 我们可以重写debugFillProperties来打印传递给窗口小部件的参数是
  • 更好的错误消息
    如果发生异常(例如ProviderNotFound),框架将为您提供当前正在构建的小部件的名称。如果仅将小部件树拆分为功能+ Builder,则错误将没有有用的名称
  • 可以定义键
  • 可以使用上下文API
  1. 功能:

总体而言,由于这些原因,在类上使用函数来重用窗口小部件被认为是不好的做法。
可以,但是将来可能会咬您。


评论不作进一步讨论;此对话已转移至聊天
塞缪尔·柳

10

在过去的两天里,我一直在研究这个问题。我得出以下结论:可以将应用程序的各个部分分解为功能。理想的情况是这些函数返回一个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。如果我错了,请纠正我。


有人可以解释为什么我说的是错误的吗?我的意思是,我认为考虑到否决票是错误的。
Sergiu Iacob

我实际上同意你的看法。我一直打算写出更详细的差异细目,但还没有解决。随意充实您的论点,因为我认为了解小部件v方法的优缺点很重要。
TheIT

@SergiuIacob我们可以const在每种情况下在无状态类面前使用吗?还是必须要有某些情况?如果是,那是什么?
艾敦克

1
@aytunch我不认为您可以const在任何地方使用。例如,如果您有一个StatelessWidget返回Text包含变量值的的类,并且该变量在某个地方发生了变化,则该变量StatelessWidget将被重建,因此它可以显示不同的值,因此不能为const。我认为放置它的安全方法是:如果可以的话,请尽可能使用const
瑟吉·雅各布

3
我一直在辩论是否自己回答这个问题。公认的答案是完全错误的,但是Rémi为尝试帮助颤抖的社区做出了很多努力,因此人们可能不会像其他人那样仔细检查他的答案。从所有支持中可以明显看出这一点。人们只想要他们的“单一真相”。:-)
DarkNeuron

4

函数和类之间有很大的区别。


让我从头开始解释它。(仅当务之急)

  • 编程历史,我们都知道从简单的基本命令开始(例如-:Assembly)。

  • Next结构化编程附带了流程控件(例如-:if,switch,while,for等),该范例使程序员可以有效地控制程序流程,并且可以最大程度地减少循环的代码行数。

  • 接下来是过程编程,它将指令分为过程(功能)。这给程序员带来了两个主要好处。

    1.将语句(操作)分组为单独的块。

    2.可以重复使用这些功能块(功能)

但是最重​​要的是,范式并没有提供管理应用程序的解决方案。过程编程也只能用于小型应用程序。那不能用于开发大型Web应用程序(例如,银行,谷歌,YouTube,Facebook,stackoverflow等),无法创建android sdk,flutter sdk等框架……

因此,工程师需要做更多的研究来以适当的方式管理程序。

  • 最终,面向对象编程提供了用于管理各种规模应用程序的所有解决方案。(从问候世界到使用系统创建的万亿人,例如google,amazon和当今90%的应用程序)。

  • 在oop中,所有应用程序都是围绕对象构建的,这意味着应用程序是这些对象的集合。

因此对象是任何应用程序的基础。

类(运行时对象)将与这些变量(数据)相关的数据和函数分组。因此对象由数据及其相关操作组成。

[这里我不会解释oop]


👉👉👉好吧,让我们来了解一下flutter框架。👈👈👈

-Dart同时支持过程和oop,但是,Flutter框架完全通过使用classes(oop)构建。(因为大型可管理框架无法使用过程创建)

在这里,我将列出使用它们而不是使用类来制作小部件的功能的原因的列表。👇👇👇


1-大多数情况下,构建方法(子窗口小部件)调用同步和异步函数的数量。

例如:

  • 下载网络映像
  • 获取用户等的输入

因此build方法需要保留在单独的类窗口小部件中(因为build()方法调用的所有其他方法都可以保留在一个类中)


2-使用窗口小部件类,您可以创建另一个类的编号,而无需一次又一次地编写相同的代码(** Use Of Inheritance **(扩展))。

并且还可以使用继承(扩展)和多态性(覆盖)来创建自己的自定义类。(在下面的示例中,我将通过扩展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-函数不能为其参数添加条件,但是可以使用类窗口小部件的构造函数。

下面的代码示例below(此功能在框架小部件中大量使用)

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
我可以知道这如何回答OP问题吗?
CopsOnRoad '18

2
看起来我回答的是错误的部分。我试图回答Daniel的问题,即仍在调用重构的无状态窗口小部件构建方法。通过const在调用重构的无状态窗口小部件时添加关键字,该关键字只能被调用一次。
user4761410

1
好。得到它了。人们可能会否决这个答案,因为它与OP问题无关。因此,您应该删除它。无论如何,选择是您的。
CopsOnRoad
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.