作者:肉斯情- | 来源:互联网 | 2023-08-08 12:25
前面的文章我们对一个简单FlutterDemo进行了大篇幅的分析,整体上我们对Flutter的一些特性已经有所了解,但是细节上我们并没有过多展开。从现在开始,我们从Flutter开
前面的文章我们对一个简单 Flutter Demo 进行了大篇幅的分析,整体上我们对 Flutter 的一些特性已经有所了解,但是细节上我们并没有过多展开。从现在开始,我们从 Flutter 开发框架的一系列基本概念入手,逐步构建起 Flutter 应用开发完整的知识体系。
本文要讨论的是 Flutter 中一个最基本最重要的概念 —— Widget(部件)。(关于具体的 Widget 应用会在以后的开发讲解文章中涉及,本文先从基础概念开始)
一、何为 Flutter Widget?
先上定义
我们看到官方文档中给出 Widget 的解释为:
“Widgets
describe what their
view should look like given their current configuration and state.”
怎么理解呢,Flutter 中,Widget 是对视图的一种包含配置及状态信息的“描述数据”,用于约束具体的视图元素。 也就是说,Widget 是一种描述,他是渲染绘制 UI 的依据,而不是具体的 View。
很多人就纳闷了,视图就视图吧,为啥要设计一个描述数据拐个弯路,直接做成 View 不就行了吗?不着急,往下看。
响应式编程范式
要理解 Widget 的设计思想,我们首先要明确 Flutter 的设计思想,官方对 Flutter 的解释是,Flutter 是受 React 编程范式启发而设计的“现代响应式框架”,问题又来了,那么什么是“响应式”呢?
“响应式”按照标准定义的话,是一种基于“数据流”模型的声明式编程范式。所谓“声明式”,是相对于“命令式”的,我们最早接触的编程都是命令式的,同一个流程,“命令式”会将命令下达到每一个具体的步骤,直到达到目标,而“声明式”,直接声明预期的结果,程序自己完成中间的步骤,也就是说“声明式”编程范式的关键是让程序能理解你的描述,知道干什么,然后自发的去完成。
听上去很复杂?其实不然,真的不是什么很高大上的东西,只是一种思想/范式而已,你其实早就已经接触过了。比如你经常制作的 Excel 表格:
这是一个简单的统计表格,注意,有个关键的地方,“当月总数”与“总数量”都是通过 Excel 公式定义自动计算的,当我们填充好 ABCD 各月数据之后,求和结果都已经出来了,你没有直接去操作这些总和,对吧,接着看:
当你将 B 的 2 月数据调整为 30 的时候,数值提升造成的影响首先传递到了“当月总数”,致其增加,然后这一增加的影响又传递到了“总数量”,引起了一连串的基于数据流的变化,而这些变化是自发的,所以再重复一遍,自发的前提是因为响应式知道如何响应,比如我们事先给出的公式,所以我们仅仅给出 B 的数据描述,表格就能自己完成剩下的工作,这就是典型的响应式思想。
试想一下,如果采用命令式的编程方式,步骤应该是:主动增加 B 数值,主动增加“当月总数”,主动增加“总数量”,结束,emmmmm…而相比之下,响应式的步骤则是:修改 B 到目标数值,结束。所以,大家心里就有个数了,为什么现在响应式编程范式这么流行。
再论 Widget
Widget 就是基于这种响应式思想而设计出来的,我们是希望视图等一些数据通过读取 Widget 的描述信息来进行响应变化,无论是一些配置信息还是状态的变化信息,而不是直接去操作视图。即 Flutter 中的 Widget 并不能简单的理解为一种 View,比如我们知道 Android 等 Native 开发中的 View 是可以由程序员直接操作的,经常有各种 .setXXXX(XXXX) 的操作,这是命令式的方式,不符合响应式的思想,所以 View 与 Flutter 的 Widget 便不是一个概念。
二、关于 Widget,我们要了解的几点
Flutter 中 Widget 是不可变的
Flutter 中,Widget 被设计为“immutable”,类似于 Java 中的 String,你无法去修改一个已经创建的 immutable 对象。如果想去改变一个 Widget,唯一的方式是去重建他,没错,Flutter 也是这么做的。当 Widget 维持的状态发生改变时,通过State.setState
,使得相关联的 Widget 发生重建,这将作为视图更新的依据。
有人认为频繁的重建 Widget 不是什么好事情,其实首先来说,Widget 只是视图的一种描述数据,重建的过程并不直接引起视图重绘等操作,他是很轻量级的,重建并不会有太大的性能开销,另一方面,Dart 针对 Widget 经常重建这一点对内存管理做了很大的优化,采用了分代无锁 GC ,将 GC 对性能的影响降至最低,所以在这一方面不必纠结过多。
Everything is Widget
Flutter 的设计思想是“Everything is Widget”,在 Flutter 中,Widget 是一个比较宽泛的概念,无论基本部件、布局、还是手势等都是 Widget。举个例子,一般的原生开发中我们会认为 center 是的一种布局属性,而 Flutter 中,center 也设计一种 Widget,我们既然理解了 Widget 是对视图的一种“描述数据”,那么就很容易理解 center 这种 Widget 的意义,center 可以理解为“内部的视图元素居中绘制”,就是这么一段描述而已,会成为视图绘制的一种约束。
Widget 有点类似于 Android 中 XML 的角色,对视图做出了约束,但他的职责比 XML 要多,因为有一些 Widget 并不是和特定的 View 对应的,如一些手势 Widget 等。还是那句话,Flutter 中,Everything is Widget,所以需要在以后的开发中慢慢领会。
Widget 实现“多孩子”
一个普通的 Widget 只能有一个 Child,如果想要有多个孩子,应该用这种方式去实现:在 将Row
, Column
, 或者Stack
作为 Widget 的 Child 进行嵌套,这三个布局 Widget 有 Children 属性,意味着可以有多个孩子。
三、再论 StatelessWidget 与 StatefulWidget
Flutter 开发中,你大部分时间都是在与这两种 Widget 打交道,通过上一篇文章对于 StatelessWidget 与 StatefulWidget 用法的简单认识以及上文中对于 Widget 的讨论,现在回过头来看 StatelessWidget 与 StatefulWidget 就容易理解了。
StatefulWidget 相比 StatelessWidget 多了一个对于状态的维持。也就是说,对于需要维持状态信息的 Widget,我们要采用 StatefulWidget,我们在程序中通过对 state 进行更新,通过State.setState
使得 Widget 重建,最终在视图中响应状态的变化;对于不需要单独维持状态的 Widget,我们就要采用 StatelessWidget。
Flutter 鼓励我们能用 StatelessWidget 就去用 StatelessWidget,我们应该将真正需要携带 state 的 Widget 设计成 Statefulidget,其它的越简单越好。
理解 widget 与 state 的生命周期
我们要明白的是,尽管一些 StatefulWidget 与 state 紧紧关联,但是 state 的生命周期与 Widget 的生命周期是不一致的,state 可以持久的存在,需要的时候自身作出更改即可,而许多 Widget 都比较短命,伴随着 state 的更改,Widget 要经历不停的销毁和重建。
四、Widget 哲学 — 组合大于继承
Flutter 中,很多 Widget 本身通常由许多小型、单用途的 Widget 组成,结合起来产生强大的效果,类的层次结构是扁平的,以最大化可能的组合数量,这是一种非常灵活的思想,我认为每一位 Flutter 开发者都应该将这种思想深刻领会。 Widget 是相当灵活的,灵活就会带来挑战,我认为不仅仅是对于一些基本部件的封装遵循“组合大于继承”,在实际的业务开发中也应该时刻注意拿捏项目中各个功能 Widget 的职责分离与封装粒度,而且这个问题将会在项目变得庞大起来后显得更为重要。
以之前的计数器 Demo 为例,点击浮动按钮产生计数变化仅仅用了一个 MyHomePage 这个 StatefulWidget:
class MyHomePage extends StatefulWidget {...}
class _MyHomePageState extends State<MyHomePage> {
// Text 用来显示计数器数值 // floatingActionButton 用来触发计数增加事件修改 state }
这个 Demo 实现一个增加计数的计数器功能是没有任何问题的,但是如果我们想复用其中的“数值显示部件”或者“增加计数按钮”就很困难了,如果换一种方式去封装 Widget:
// 数值显示 Widget class CounterDisplay extends StatelessWidget {...}
// 计数增加 Widget class CounterIncrementor extends StatelessWidget {...}
// 计数数值 Widget class Counter extends StatefulWidget {...}
class _CounterState extends State<Counter> {
...
// 调用封装好的 wiget CounterIncrementor(onPressed: _increment),
CounterDisplay(count: _counter),
...
}
这样抽离出来各个功能的 Widget 是不是更符合“职责分离”的原则,有利于各部分 Widget 的复用,也避免了某些 Widget 过于臃肿难以维护的情况。这毕竟只是个简单的 Demo,试想如果一个复杂的 UI 页面,将诸多 Widget 不加分离直接写在一起的话,恐怕连阅读代码都很困难。
总结
Widget 是 Flutter 中最基本也是最重要的概念,我们在今后的开发过程中要时时刻刻和他打交道。所以,我们对于 Widget 的学习,不能仅仅停留在会应用的层面,而是应该从 Flutter 的设计思想,Widget 的设计背景上深入地去了解,再结合平时使用 Widget 的一些经验,一些疑问,不断地深化对 Widget 这一概念的认识。万丈高楼平地起,我相信基础打好了,这些最基本的概念理解透彻了,以后面对一些复杂的项目才能更加得心应手。
参考资料
- Introduction to widgets
- 闲鱼技术:深入了解Flutter界面开发(强烈推荐)
- 闲鱼技术:Flutter快速上车之Widget