[EP-005] 优雅抽离小组件

本文介绍了关于抽离小组件的最佳实践,并提及了在官方视频中没有提到的一些注意事项和特殊情况。

总是会有新手开发者说:“Flutter 很难用,简直是嵌套地狱。”oh,well,对啊,那你来找优雅实践实验室啊。

虽然不是 Flutter 代码,但是很经典的表情包

问题提出

在 Flutter 开发中常见的需要抽离小组件来解决一些嵌套地狱的情况,但是在哪里抽离?应该怎么抽离?事实上这已经成为一种面试题而存在了,毕竟会抽离小组件的 Flutter 开发者,代码质量一般会高一些。如果你喜欢视频教学,我相信你会喜欢这个来自 Flutter 官方的视频:

https://youtu.be/IOyq-eTRhvo?si=Mmn_p2suHA2vBB-A

​实践探索

抽离小组件的方式其实有三种,虽然视频中只提及了两种——helper method 和小组件,还有一种是抽离为局部变量。这在快速修复中可以找到:

抽离为小组件的争议应该不大,视频里也说得很明白了,重点在于抽离为局部变量还是抽离为函数方法。

这里给出茴香豆的四种写法:

  1. 抽离为函数,放在和 build 函数外;
  2. 抽离为函数,放在 build 函数内部,return 语句之前;
  3. 抽离为常量,放在和 build 函数外;
  4. 抽离为常量,放在 build 函数内部,return 语句之前。

可能有小伙伴看到这里,就想骂人了,为什么我们要把那么简单的问题搞得那么复杂,其实存在即合理,四种方案我都看见过。1 应该是常见的写法;4 是大家最熟悉的 Container 的内部实现的写法,干净优雅。

3 是违规的写法,但群里确实有人提出来过,由于没有上下文和生命周期,定义出来的小组件很容易出现奇奇怪怪的错误;2 是接手过一个愚蠢的 React 开发者转 Flutter 开发写出来的,他期望所有的代码都长得和 React 一样,但是:

  • 但 Flutter 没有函数组件的概念,函数化并不会优化刷新范围;
  • 方案 2 相对于方案 4 来说,4 可以少编写额外的 (){} ,而且保证了函数是执行逻辑,变量存放的是小组件,即使是使用 flutter_hooks,也更可读更清晰。
  • 方案 2 相对于方案 1 来说,虽然减少了对 context 的传递,但是增加对 context 的隐性依赖,具体表现为无法轻松的将函数直接提出到小组件之外了(quick fix 也失效)。
  • 而且既然是使用了 flutter_hooks 的写法,方案 1 加上 useContext() 钩子将更方便传递上下文,虽然在 Flutter 中,这个钩子无法共享状态。

那唯一有争议的也就是 1 和 4,这里我们更推荐使用方案 4,原因如下:

  • 这种写法是官方认可的,在 Container 中使用的;
  • 不需要传递 context 到函数中,因为你就在上下文内;
  • 你可以执行任何条件/循环语句,而不担心可读性,因为在 build 函数内总是顺序阅读,而不需要在函数间跳转;
  • build 函数外编写的函数,不应该带有组件,而应该只是一些纯粹的业务功能,例如:设置监听器、定义异步初始化、局部状态管理等;同理,现在你可以保证所有的 UI 都在 build 函数中可见,不需要四处翻找。

优雅实践

抽离位置

首先,关注分离点,哪些部分是重复的,或者哪个位置开始可以提取出来作为新的小组件,这个比较吃经验,我只能给出一些参考:

  1. 一般的 Row/Column 下的子组件可以抽离为子组件,但是其中的 Expaned/Flexible 组件不包括在内。Stack 布局的 Positioned 同理;
  2. 小组件可能有些修饰性的父组件,例如 Padding、Center 等,不应该和小组件一起抽离;
  3. 需要刷新状态的子组件,一定要避开其他无刷新需求的父组件单独提出来。(也可以使用消费者组件去约束刷新范围);

抽离方式

官方的视频中的抽离方式有两种,一种是作为小组件单独提出来,一种是作为组件内部的函数提出来(helper method),但这里不再比较两种抽离方式的作用和优劣,省流一下就是:

如果没有特殊需求,抽离为小组件是最好的。

抽离为小组件

抽离为小组件之后也有一些需要考虑的问题,这里直接给出最佳实践:

  1. 命名:抽离的小组件类名最好可以直接描述小组件的作用,避免阅读负担。
  2. 私有化:如果其他页面不需要复用,应放置于当前文件下,类名前加 _ 定义为私有小组件;
  3. 复用:如果其他页面需要复用,应考虑单独创建同名文件,注意使用蛇形命名法,然后一个公共组件一个文件;
  4. 抽离状态:被抽离的小组件,如果没有特殊需求,应尽可能不要通过参数传递状态,而是交由状态管理组件去共享。

抽离为局部常量

但是有时候还是会面对一些情况,我有一个很大的,无状态的页面,但是没有抽离出来刷新子组件的需求,只是单纯需要分解一下代码结构,不想创建太多无意义的小组件

  1. 位置:抽离为局部常量的组件,应放置于 build 函数内,组件的原语依赖之下和 return 语句之间;
  2. 逻辑性:你可以使用 if 语句来组装需要的小组件,这可能会比传统写在 return 语句中更繁琐,但是在处理复杂的语句中会更为清晰(因为传统的三目运算可能会找不到孩子);
  3. 复用:虽然这种程度复用优化不大,比如共用的 SizedBox(width: 4) 。复杂的 item 都被抽离为小组件了;
  4. 常量:尽可能使用 const 而不是 final 修饰组件,可以获得更优的性能的同时,也避免和普通的状态常量混淆(虽然快速修复会教你做事)。
  @override
  Widget build(BuildContext context) {
    final navigatorState = Navigator.of(context);
    final scaffoldMessengerState = ScaffoldMessenger.of(context);

    const dismissible = Dismissible(
      key: Key('value'),
      child: Card(
        child: ListTile(
          leading: Icon(Icons.add),
          title: Text('data'),
        ),
      ),
    );

    return ...;

抽离技巧

这里我们使用 vscode 举例,as 类似的操作不再重复。

  1. 鼠标单击需要抽离的小组件;
  2. 使用快速修复 Ctrl + .
  3. 选择 Extract Widget 抽离为小组件,或者选择 Extract Local Variable 抽离为局部常量;
  4. 重命名。

千万不要在评论区问我如何选中一个小组件的全部代码,然后复制粘贴,工具都给得有的,我们需要优雅的实践。

Read more

[TS-001] Flutter 开发环境搭建思路

大家好,这里是优雅实践实验室,这一节将介绍如何搭建 Flutter 开发环境。 Flutter 开发环境是由什么组成的 eplab 只提供思路,不会手把手教学。 所以从思路出发。当提及配置 Flutter 开发环境的时候,我们到底在配置什么? 由 Flutter 的设计可知,Flutter 是编译后嵌入到目标平台的。 所以我们需要同时具备 Flutter 开发环境和目标平台的开发环境。 * Flutter 开发环境 * Flutter SDK * dart SDK * pub 命令 * IDE + Flutter 插件 * git 等环境杂项 * 目标平台的开发环境(按须配置) * 目标平台 SDK * 目标平台的模拟器(可选) Flutter 开发环境 需要配置基础的 Flutter SDK、IDE 及其插件、和一些杂项开发环境。

By lilua

[TS-999] Flutter 介绍

这个视频核心目的是扫盲,解答一些常见的 Flutter 相关的问题。 Flutter 跨平台开发支持哪些平台? 一般情况下说的是:生成产物可以运行在哪些平台,官方支持的是六大平台:Android、iOS、MacOS、Linux、Windows、Web。 开发平台支持使用四大平台:MacOS、Linux、Windows、ChromeOS。 容易被忽略的是 Flutter 是一个优秀的开源项目,良好的分层架构和可移植的 Dart 语言,让它本身就支持编译到任意一个嵌入式设备中,比如和丰田合作的车载系统、腾讯曾出过一个打印机系统、开源鸿蒙系统。 Flutter 是否支持鸿蒙开发? 正如上所示,Flutter 天生支持任何嵌入式设备开发,鸿蒙本质也是嵌入式设备。所以就技术本身而言,Flutter 是支持鸿蒙的。 就细而论,开源鸿蒙、鸿蒙Next、用户手上的鸿蒙,是否都可以用 Flutter 开发呢?还是那句话,技术本身是支持的,无论鸿蒙怎么变化。 Flutter

By lilua

[TS-000] Flutter 教学视频逐字稿介绍

这是优雅实践实验室产出视频之前的需要提前准备好的逐字稿脚本(Transcript),将会记录所有收集到的文档资料、代码片段、旁白台词和最终产出的视频。 💡注意:视频内容可能会过期,但是逐字稿会更新,在必要的时候重置当期对应的教学视频。 色彩搭配 主色 #042B59 #0553B1 #027DFD 辅色 #F25D50 #FFF275 #6200EE #1CDAC5 搭配方法 使用 Material Design 的配色,将主色和辅色两两搭配,得到一套主题,并遵循配色规范使用主题色卡。

By lilua