美文网首页
重构如何改善Flutter应用程序的可读性、可维护性以及提升性能

重构如何改善Flutter应用程序的可读性、可维护性以及提升性能

作者: whqfor | 来源:发表于2021-09-06 15:34 被阅读0次

    原文地址:How refactoring improve readability, maintainability and performance optimization of your Flutter application
    原作者:Jonathan Monga
    读后感:
    这篇文章是关于如何组织代码结构的,如何编写Flutter代码,才能使代码有更好的可读性、可维护性,并且带来更好的性能呐,之前也翻译过一篇相似的文章Flutter Widget瘦身,两篇文章看完,想必会给你带一些收益。

    前言

    我们都同意widget 树是你在UI中所获得的东西,并且同意它完全是关于Flutter widget的,因此你可以将你的widget相互嵌套。无论你的UI是简单还是复杂,当你的UI简单时,即使几周后回来阅读你的代码,它也很容易阅读,并且性能很好,因为它展示的内容很少。但是当你的应用界面比较复杂时,这会促使你嵌套大量的widget,代码的可读性、可维护性降低,程序的效率也会降低。

    我知道,对于初学者来说,很容易没有重构代码的文化,一旦注意力转移到其他事情上,初学者就会满足于widget的嵌套、嵌套、嵌套,这就是产生很深的widget树的原因。对于像我这样的新手Flutter开发者来说,这是很常见的现象,好吧,既然问题已经暴露出来了,我们怎么避免?如何以一种不陷入非常深的widget树的方式进行编码呐?

    在我之前已经有不少人探讨过这个问题了,但我认为还是值得在花点时间再谈论一下。这个经常困扰我们的问题的答案就是代码重构。既然你已经得到了答案,那么就不要再拖延重构你的代码啦。下面我将用不同的技术,向你展示如何进行代码重构。

    在向你展示如何重构代码之前,让我们使用此UI的代码:


    Weather Stats.png

    这个很漂亮的UI来自于https://github.com/JideGuru/weather_neumorphism_ui,这里并没有恶意,我不认为我比Olusegun Festus Babajide更厉害,以至于我有权利对他的代码做点评。同样你如果找到一些我的代码,我相信,你也会发现很多值得抱怨的地方。

    不,这不是下流或者傲慢的行为,我将要做的无非只是专业的评论,这是我们都应该乐于做的事情,当完成时,我们应该欢迎它。通过这样发表评论,可以促使我们学习,医生这样做、飞行员这样做、律师这样做,我们程序员也应该学习这样做。补充一点:Olusegun Festus Babajide不仅是一位很好的Flutter开发人员,并且有勇气和善意,愿意将他的代码免费提供给整个社区,他把它提供给所有人看,并邀请公众使用和监督,这样做很赞。

    这是现在的代码:

    
    class Home extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            leading: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Container(
                  height: 40,
                  width: 40,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(10),
                    color: Theme.of(context).primaryColor,
                    boxShadow: [
                      BoxShadow(
                        offset: Offset(3, 3),
                        color: Colors.black12,
                        blurRadius: 5,
                      ),
                      BoxShadow(
                        offset: Offset(-3, -3),
                        color: Colors.white,
                        blurRadius: 5,
                      )
                    ],
                  ),
                  child: Icon(
                    Icons.arrow_back_ios,
                    size: 14,
                  ),
                ),
              ],
            ),
            centerTitle: true,
            elevation: 0,
            title: Text(
              "${Constants.appName}",
              style: TextStyle(
                fontSize: 25,
                fontWeight: FontWeight.w900,
              ),
            ),
          ),
          body: ListView(
            padding: EdgeInsets.symmetric(horizontal: 20),
            children: <Widget>[
              Container(
                height: 100,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Container(
                      height: 70,
                      width: MediaQuery.of(context).size.width,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(10),
                        color: Theme.of(context).primaryColor,
                        boxShadow: [
                          BoxShadow(
                            offset: Offset(3, 3),
                            color: Colors.black12,
                            blurRadius: 5,
                          ),
                          BoxShadow(
                            offset: Offset(-3, -3),
                            color: Colors.white,
                            blurRadius: 5,
                          )
                        ],
                      ),
                      child: Padding(
                        padding: EdgeInsets.symmetric(horizontal: 20),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.center,
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: <Widget>[
                            Row(
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: <Widget>[
                                Text(
                                  "Period",
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    color: Theme.of(context).textTheme.caption.color,
                                  ),
                                ),
    
                                SizedBox(width: 30,),
                                Text(
                                  "Last 30 days",
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                  ),
                                ),
                              ],
                            ),
    
                            Container(
                              height: 40,
                              width: 40,
                              decoration: BoxDecoration(
                                borderRadius: BorderRadius.circular(10),
                                color: Theme.of(context).primaryColor,
                                boxShadow: [
                                  BoxShadow(
                                    offset: Offset(3, 3),
                                    color: Colors.black12,
                                    blurRadius: 5,
                                  ),
                                  BoxShadow(
                                    offset: Offset(-3, -3),
                                    color: Colors.white,
                                    blurRadius: 5,
                                  )
                                ],
                              ),
                              child: Icon(
                                Icons.arrow_forward_ios,
                                size: 14,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
    
              SizedBox(height: 20,),
    
              Container(
                height: 300,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Container(
                      height: 280,
                      width: MediaQuery.of(context).size.width,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: Theme.of(context).primaryColor,
                        boxShadow: [
                          BoxShadow(
                            offset: Offset(6, 6),
                            color: Colors.black12,
                            blurRadius: 5,
                          ),
                          BoxShadow(
                            offset: Offset(-6, -6),
                            color: Colors.white,
                            blurRadius: 5,
                          )
                        ],
                      ),
                      child: Stack(
                        children: <Widget>[
                          Align(
                            alignment: Alignment.center,
                            child: Icon(
                              Feather.loader,
                              size: 250,
                              color: Theme.of(context).accentColor,
                            ),
                          ),
                          Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: <Widget>[
                              Container(
                                height: 200,
                                width: MediaQuery.of(context).size.width,
                                decoration: BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: Theme.of(context).primaryColor,
                                  boxShadow: [
                                    BoxShadow(
                                      offset: Offset(3, 3),
                                      color: Colors.black12,
                                      blurRadius: 5,
                                    ),
                                    BoxShadow(
                                      offset: Offset(-3, -3),
                                      color: Colors.white,
                                      blurRadius: 5,
                                    )
                                  ],
                                ),
                                child: Column(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  crossAxisAlignment: CrossAxisAlignment.center,
                                  children: <Widget>[
                                    Icon(
                                      Feather.thermometer,
                                      color: Theme.of(context).accentColor,
                                      size: 40,
                                    ),
                                    SizedBox(height: 20,),
                                    Text(
                                      "7°C",
                                      style: TextStyle(
                                        fontWeight: FontWeight.bold,
                                        fontSize: 22,
                                        color: Theme.of(context).accentColor,
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
    
              SizedBox(height: 20,),
    
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  Container(
                    height: 150,
                    width: 130,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(10),
                      color: Theme.of(context).primaryColor,
                      boxShadow: [
                        BoxShadow(
                          offset: Offset(3, 3),
                          color: Colors.black12,
                          blurRadius: 5,
                        ),
                        BoxShadow(
                          offset: Offset(-3, -3),
                          color: Colors.white,
                          blurRadius: 5,
                        ),
                      ],
                    ),
                    child: Padding(
                      padding: EdgeInsets.all(15),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          Icon(
                            Feather.cloud_snow,
                            size: 40,
                            color: Theme.of(context).accentColor,
                          ),
    
                          Text(
                            "Cool",
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 22,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
    
                  Neumorphic(
                    height: 150,
                    width: 130,
                    status: NeumorphicStatus.convex,
                    decoration: NeumorphicDecoration(
                      borderRadius: BorderRadius.circular(10),
                    ),
                    child: Padding(
                      padding: EdgeInsets.all(15),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          Icon(
                            Feather.sun,
                            size: 40,
                            color: Colors.deepOrange,
                          ),
    
                          Text(
                            "Warm",
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 22,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
              SizedBox(height: 20,),
    
              Neumorphic(
                status: NeumorphicStatus.convex,
                height: 50,
                decoration: NeumorphicDecoration(
                  borderRadius: BorderRadius.circular(10),
                ),
                child: Center(
                  child: Text(
                    "Update Settings",
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 16,
                      color: Theme.of(context).accentColor,
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }
    
    wow.png

    那么让我们看看如何使这一切井然有序。

    1、使用方法重构

    我想你在某些地方已经看到了这种技术而没有意识到。该技术只是将widget作为方法调用的返回值,进行封装使用。假设在Flutter中一切都是widget,那么任何参与组成UI的类都继承自Widget类,该方法的返回值可能是任何一个widget类或者一些特定的类,例如容器类container、row、column等。

    继续往下看,方法中的Widget可以依赖父widget的BuildContext实例或对象。这就是问题的来源,记住BuildContext对象知道widget在widget tree中的位置。既然此方法依赖于主BuildContext,那么当父widget重绘时,此方法也将强制重新组装、重新创建或者重绘其内部的widget。或者如果该方法也调用了其他依赖于父widget的BuildContext的方法,也会带来副作用,所有方法绘制他们的widget的次数将会和绘制父widget的次数一样多。无论哪种情况,这都不是我们重构后所期望的行为。

    使用这种方法,我们将widget分割开来,这当然能够带来可读性及可维护性的提升,但是对于性能优化,并没有什么用处。当widget数量增加时,我们UI的性能在配置更改期间将会下降,例如屏幕旋转。

    下面是两个方法的示例:

    Column _buildLeadingColumn(BuildContext context) {
        return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Container(
                height: 40,
                width: 40,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  color: Theme.of(context).primaryColor,
                  boxShadow: [
                    BoxShadow(
                      offset: Offset(3, 3),
                      color: Colors.black12,
                      blurRadius: 5,
                    ),
                    BoxShadow(
                      offset: Offset(-3, -3),
                      color: Colors.white,
                      blurRadius: 5,
                    )
                  ],
                ),
                child: Icon(
                  Icons.arrow_back_ios,
                  size: 14,
                ),
              ),
            ],
          );
      }
    
    Widget _buildRow(BuildContext context) {
        return Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              "Period",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                color: Theme.of(context).textTheme.caption.color,
              ),
            ),
            SizedBox(
              width: 30,
            ),
            Text(
              "Last 30 days",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
              ),
            ),
          ],
        );
      }
    

    我们使用Visual Studio Code作为代码编辑器(AS也一样),并按照以下步骤进行重构:
    1.打开任何.dart文件
    2.将光标放在第一个widget上,然后右击,在我的场景中,是在Row、Container或者Column上。
    3.选中Refactor >Extract Method
    4.在提取方法的弹窗中,输入_buildRow作为方法名,注意方法前的下划线,让Dart知道这是一个私有方法。
    5.Row widget现在替换为了_b方法uildRow(),滚动到代码底部,方法和widget都得到了很好的重构。
    6.继续重构其他的Rows、Columns、Containers和Stack Widget。

    这种方式增加了代码的可读性,widget树的主要组成部分被分割成了非常简单的方法,这种方式的好处是纯粹和简单的代码可读性和可维护性,作为回报失去了优化性能,如果你想看更多内容,请转到底部的引用部分。

    2、使用局部变量重构

    和第一种重构方式有些相识,只不过这里使用局部变量,包括使用final变量初始化widget。在这里一样是将widget树的主要部分分割成多个,这增加了代码的可读性和可维护性。
    在这种情况下,虽然我们的widget使用final来初始化变量,但是仍然使用的是父widget的BuildContext,当框架重绘父widget时,局部变量也将会被重绘。这增加了可读性和可维护性,你的widget树将会变浅,但是不会优化性能。

    下面是一个带有常量的的重构代码示例:

    final rowConstant = Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              "Period",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                color: Theme.of(context).textTheme.caption.color,
              ),
            ),
            SizedBox(
              width: 30,
            ),
            Text(
              "Last 30 days",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
              ),
            ),
          ],
        );
    

    我们使用Visual Studio Code作为代码编辑器(AS也一样),并按照以下步骤进行重构:
    1.打开任何.dart文件
    2.将光标放在第一个widget上,然后右击,在我的场景中,是在Row、Container或者Column上。
    3.选择 Refactor > Extract Local Varialble
    4.在我们的例子中,将局部变量命名为rowConstant,注意我们使用final进行修饰,告诉Dart这是一个常量。
    5.Row widget替换为了rowConstant最终变量。滚动带代码顶部,局部变量和widget都得到了很好的重构。
    6.继续重构其他的Rows、Columns、Containers和Stack Widget。

    3、使用widget class重构

    这种方式允许你使用继承自StatelessWidget或者StatefullWidget的类,来隔离widget子树,还允许你创建可重用的widget,并且可以将它们分布在相同或不同的dart文件中,这样你就可以在程序的任何地方引入或者使用这些文件。警告!这些类的构造函数必须以const关键字开头,再次感谢Dart,以const开头声明的构造函数,会告诉Dart缓存和重用这些widget,与此相反的是其它widget将会被重绘。

    当你要创建此类的对象时,不要忘记使用const关键字。通过这样做,当其他widget在widget树中更改状态时,此widget将不会被重建。如果遗漏了const关键字,父widget重绘多少次,我们的widget也将会跟着重绘多少次,因此需要留心。

    这样的widget类依赖它自己的BuildContext,而不是像重构成方法或者变量的那样依赖于父widget的。BuildContext负责管理widget在widget树中的位置。

    现在让我们看看使用这种方式的小例子:

    class PaddingWidget extends StatelessWidget {
      const PaddingWidget({
        Key key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Text(
                    "Period",
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Theme.of(context).textTheme.caption.color,
                    ),
                  ),
                  SizedBox(
                    width: 30,
                  ),
                  Text(
                    "Last 30 days",
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 16,
                    ),
                  ),
                ],
              ),
              Container(
                height: 40,
                width: 40,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  color: Theme.of(context).primaryColor,
                  boxShadow: [
                    BoxShadow(
                      offset: Offset(3, 3),
                      color: Colors.black12,
                      blurRadius: 5,
                    ),
                    BoxShadow(
                      offset: Offset(-3, -3),
                      color: Colors.white,
                      blurRadius: 5,
                    )
                  ],
                ),
                child: Icon(
                  Icons.arrow_forward_ios,
                  size: 14,
                ),
              ),
            ],
          ),
        );
      }
    }
    

    我们使用Visual Studio Code作为代码编辑器(AS也一样),并按照以下步骤进行重构:
    1.打开任何.dart文件
    2.将光标放在第一个widget上,然后右击,在我的场景中,是在Row、Container或者Column上。
    3.选择 Refactor > Extract Widget
    4.在我们的例子中,将类名命名为PaddingWidget
    5.Padding widget替换为了PaddingWidget类。滚动带代码底部,类和widget都得到了很好的重构。
    6.继续并重构其他Padding(PaddingWidgets class)、Rows(RowsAndColumnWidget class)widget。

    抱歉,有太多内容需要消化,我总结一下:你不仅在可读性和可维护性上有所收获,并且性能也会有很大提升。因为当父widget重绘时,并不是所有widget都会被重绘,他们只构建一次。

    结论

    在这篇文章中,你了解到了widget树是widget嵌套的结果,随着widget的增加,widget树会迅速扩展并且降低代码的可读性以及可管理性,这被称之为整个widget树。为了提高代码的可读性和可管理性,你可以将widget分割成独立的widget类,创建一个浅的widget树。在每个程序中,你都应该尽量保持widget树层级浅。通过使用widget类的重构方式,你可以在Flutter子树的重构中获益,这将会提升性能。

    感谢阅读我的文章,欢迎进行评论。

    引用:
    Beginning_Flutter

    Refactoring a Flutter Project -- a story about progression and decisions

    Refactorings and Code Fixes

    JidiGutu/weather_neumorphism_ui

    Flutter Community

    相关文章

      网友评论

          本文标题:重构如何改善Flutter应用程序的可读性、可维护性以及提升性能

          本文链接:https://www.haomeiwen.com/subject/rjgewltx.html