解决 Flutter 嵌套过深,是选择函数还是自定义类组件?

前言

初学 Flutter 的时候,一个很大的感受就是组件嵌套层级很深,写下来的代码找对应的括号都找不到。比如下面这种情况,从最外层的 Scaffold 到最里层的Image.asset,一共有7层组件嵌套。这还不算多的,最夸张是见过一个表单页面写了10多层,代码的阅读体验非常糟糕,而且如果不小心删除了一个括号要找半天才对应得上。当然,通过 VSCode 彩虹括号(Rainbow Brackets)这个插件能够一定程度上解决括号对称查找得问题,但是代码的可维护性、阅读体验还是很差。自然而然,大家会想到拆分。拆分有两种方式,一种是使用返回Widget 的函数,另一种是使用 StatelessWidget,那这两种该如何选择呢?

繁多的括号.png

拆分原则

在关于这个问题的讨论上,2年前 StackOverflow 有一个经典的回答:使用函数和使用类来构建可复用得组件有什么区别?其中提到得一个关键因素是 Flutter 框架能够检测组件树的类对象,从而提高复用性。而对于私有的方法来说 Flutter 在更新的时候并不知道该如何处理。


StatelessWidget和函数式组件.png

答主也对比了使用类和函数的优劣势。使用类构建的方式:

  • 支持性能优化,比如使用 const 构造方法,更细颗粒度的刷新;
  • 两个不同的布局切换时,能够正确地销毁对应得资源。这个我们在上篇讲 StatefulWidget 的时候有介绍过。
  • 保证正确的方式进行热重载,而使用函数可能破坏热重载。
  • 在 Widget Inspector 中可以查看得到,从而可以方便我们定位和调试问题。
  • 更友好的错误提示。当组件树出现错误时,框架会给出当前构建得组件名称,而如果使用函数的话则得不到清晰得名词。
  • 可以使用 key 提高性能。
  • 可以使用 context 提供的方法(函数式组件除非显示地传递 context)。

使用函数构建组件唯一的优势就是代码量会更少,不过这可以通过 functional_widget 插件解决,functional_widget是一个通过注解将和函数式组件构建方式自动转换为类组件的代码生成插件)。

示例对比

下面我们看一段没有拆分的代码,这个仅仅是示例代码,没有任何实际意义。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('Counter: $_counter'),
        Container(
          child: Column(
            children: [
              Text('Hello'),
              Row(
                children: [
                  Text('there'),
                  Text('world!'),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }
}

括号有点多,对吧,一眼看过去都懵圈了 —— 这也是很多初次接触 Flutter 的人吐槽地方,可以说让不少人直接放弃了! 最直接的方式就是将部分代码抽离成为一个私有方法,比如像下面这样。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  Widget _buildNonsenseWidget() {
    return Container(
      child: Column(
        children: [
          Text('Hello'),
          Row(
            children: [
              Text('there'),
              Text('world!'),
            ],
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('Counter: $_counter'),
        _buildNonsenseWidget(),
      ],
    );
  }
}

将深度嵌套的组件代码单独抽成了一个返回 Widget 的私有方法,看起来确实让代码简洁不少。
那么问题就解决了吗?我们来看一下当状态改变的时候会发生什么。
我们知道,当状态变量_counter改变后,Flutter 会调用 build 方法刷新组件。这会导致 _buildNonsenseWidget 这个方法在刷新的时候每次都会被调用,意味着每次都会创建新的组件来替换旧的组件,即便两个组件没有任何改变。而事实上,我们应该只重建那些变化的组件,从而提高性能。
现在再来看使用类组件的方式,实际上有代码模板的情况下,编写一个 StatelessWidget 非常简单。使用类组件后的代码如下所示。代码确实会比函数的方式多,但是实际上大部分不需要我们手敲。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('Counter: $_counter'),

        // The deeply nesting widget is now refactored into a
        // stateless const widget. No more needless rebuilding!
        const _NonsenseWidget(),
      ],
    );
  }
}

class _NonsenseWidget extends StatelessWidget {
  const _NonsenseWidget();

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          Text('Hello'),
          Row(
            children: [
              Text('there'),
              Text('world!'),
            ],
          ),
        ],
      ),
    );
  }
}

这里注意,以为这个_NonsenseWidget 在组件得声明周期不会改变,因此使用了 const 的构造方法。这样在刷新过程中,就不会重新构建了!关于 const 可以参考之前的两篇文章。

关于 StatefulWidget,你不得不知道的原理和要点!

解密 Flutter 的 const 关键字

总结

相比使用函数构建复用的组件代码,请尽可能地使用类组件的方式,而且尽可能地将组件拆分为小一点的单元。这样一方面可以提供精确的刷新,另一方面则是可以将组件复用到其他页面中。如果你不想改变自己得习惯,那么可以考虑使用 functional_widget 这个插件来自动生成类组件。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,682评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,277评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,083评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,763评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,785评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,624评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,358评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,261评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,722评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,030评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,737评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,360评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,941评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,057评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,237评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,976评论 2 355

推荐阅读更多精彩内容