写点什么

从相亲来看 Flutter 的 StatefulWidget 和 StatelessWidget

作者:岛上码农
  • 2022 年 5 月 13 日
  • 本文字数:3290 字

    阅读完需:约 11 分钟

从相亲来看Flutter 的 StatefulWidget 和 StatelessWidget

楔子

雷思是一名程序员,年龄老大不小了,家里急啊,老催他相亲。可是雷思同学对相亲不太上心,相亲网站应付似的填了点东西,然后截个图给他老娘发过去,说是已经在相亲网站挂出去了。其实他压根就没当回事——程序员嘛,哪里会缺对象,现在用了 Dart 语言,连 new 都不需要了。这一天,雷思正在找 bug,刚好定位到一个对象的内存分配问题,正准备动手改呢,叮咚,手机弹了一个消息:“雷思,你好,小芙给你发送了一个见面邀请,是否接受?”雷思正准备划掉消息,瞥了一眼头像,突然有点心动,点了个“接受”按钮。于是,雷思开始了他的第一次网上相亲之旅。

相亲的准备

小芙十分注重个人形象,而且又十分善于掩藏自己的内心状态。从表面看她和普通的女孩子一样,但是内心戏却很足。


class Xiaofu extends StatefulWidget {  Xiaofu({Key key}) : super(key: key) {    print('constructor: 小芙');  }
@override _XiaofuState createState() => _XiaofuState();}
class _XiaofuState extends State<Xiaofu> { //这是小芙的内心戏,表面看不到}
复制代码


相反,程序员雷思很简单,标准的单纯技术男。


//简简单单的程序员——雷思class Leisi extends StatelessWidget {  Leisi({Key key}) : super(key: key) {    print('constructor: 雷思');  }}
复制代码


约定好的相亲时间还有三个小时,小芙就开始梳妆打扮起来,这雷思可是她从好多简历里挑出来的,必须精心准备。她始终记得妈妈教导过——女人面对男人时得优雅一点。


@overridevoid initState() {  super.initState(); //妈妈教导过的话  print('initState:小芙花了2小时化妆');}
复制代码


这个时候呢,雷思还在写代码。


相亲的开场白

相亲时间到了,雷思秉承着从国外名著学到的绅士习惯,提前了 15 分钟达到了约定的餐厅——这地点是相亲网站给他们推荐的。餐厅看着挺不错,估计消费不低。这肯定是相亲网站的合作餐厅,估计能给他们返不少点——雷思的互联网平台思维下意识就上来了。而小芙呢,自然是要等雷思到了之后才会出现。


@overridevoid didUpdateWidget(oldWidget) {  super.didUpdateWidget(oldWidget);  print('didUpdateWidget:小芙出现了');}
复制代码


“你好!不好意思,让你久等了!”小芙面带微笑优雅地挥手向雷思打招呼。雷思没感觉到她的不好意思,只是突然有些局促,“你……你好!呃,没事,我也才到一会。”小芙心里飘过三个字——没经验,当然,这些雷思看不出来。

相亲的过程

见了面了,招呼也打了,开始点菜吧。“你来点吧!”雷思继续遵循他的绅士风度,女士优先嘛!“你来吧,我也不懂吃些什么。”小芙自然礼貌性地推托一下。“呃,好吧!你喜欢吃什么?”“随便吧,我对吃没什么特别讲究。”“那我就随便点了啊。”雷思没有注意到小芙的表情表了,真的随便点了两个菜。


@overrideWidget build(BuildContext context) {  print('build:雷思');  return Center(    child: Text('雷思随便点了两个菜'),  );}
复制代码


@overrideWidget build(BuildContext context) {  print('build:小芙');  return Center(    child: Column(children: [      Text('表情:$_face'),      TextButton(          onPressed: () {            setState(() {              _face = '失望';              print('小芙的表情变了');            });          },          child: Text('改变表情')),    ]),  );}
复制代码



接下来的过程就有点无聊了,结果自然是吃完各回各家。

结局

“相亲也就这样吧。”雷思没多想,回家洗个澡,翻了会掘金的沸点——这个比相亲有趣多了!而小芙回到家,失望之情依然没有消散。她想不明白怎么就一时脑热要决定和这个叫雷思的人相亲,程序员真的像外界传言的那样无趣(简单)!过了相当一会,才平复自己的懊恼的情绪。


@overridevoid deactivate() {  print('deactivate:小芙的情绪平复了');  super.deactivate();}
复制代码


正当小芙准备将雷思从关注列表删除时,她突然想明白了当时为什么会决定和雷思相亲,应该是这个名字和她喜欢的蕾丝一样的读音吧。她点了一下“取消关注”,就这样,雷思从她的关注列表中消失了。


@overridevoid dispose() {  print('dispose:小芙取消关注雷思');  super.dispose();}
复制代码

后记

从雷思和小芙的相亲故事可以看到,雷思(StatelessWidget)作为无状态组件,真的很简单。而小芙(StatefulWidget)作为有状态组件,内心戏十足。我们以一个演示页面为例。


class StateDemoPage extends StatelessWidget {  StateDemoPage({Key key}) : super(key: key);
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('雷思和小芙的故事'), ), body: Column( children: [ Xiaofu(), Leisi(), ], ), ); }}
复制代码


首次进入该页面时,打印出来的内容如下,可以看到小芙多了 2 个步骤,分别是initStatedidChangeDependencies。也就是有状态组件会多经历两个步骤:


  • initState:初始化状态,在对象插入到组件树后调用。而在组件的状态创建和初始化状态之间,框架会给状态绑定一个 BuildContext。通常我们会在这里请求网络或其他界面所需要的数据。

  • didChangeDependencies:当状态依赖的对象改变的时候被调用(例如一个 InheritedWidget 被改变时),当 initState 完成后(即初始化完成后)会马上调用该方法。该方法调用后会调用 build 重新构建组件树。因为每次状态依赖改变时都会调用 build 方法,因此通常不需要子类重载该方法,除非是有些负荷过重的任务(如单次的网络请求)不想每次build 时调用。


flutter: constructor: 小芙flutter: constructor: 雷思flutter: initState:小芙花了2小时化妆flutter: didChangeDependencies:小芙flutter: build:小芙flutter: build:雷思
复制代码


之后就是 build 方法了,这个在无状态和有状态组件都有。在有状态组件中,当初始化完成或调用setState如小芙的表情改变)会进行调用。


对于无状态组件,后面就没有别的方法了,但是对于有状态组件还存在四个方法:


  • reassemble:重装时调用,这个一般是在热重载的时候(我们修改完代码,直接保存后界面会随之更新)。热重载会重新构建组件,但不会初始化状态。只是会在构建完成后调用 didUpdateWidget,告知组件更新完成。

  • didUpdateWidget:当组件配置更改时调用。父组件重建时会调用树中的有状态的子组件的该方法。同时会显示一个新的同类型组件和 key。框架会更新组件的状态指向新的组件,然后调用该方法,并把旧组件传递给该方法。该方法调用后总是会调用 build 方法。通常在该方法中完成组件移除的动作,例如动画。


flutter: reassemble:小芙flutter: constructor: 小芙flutter: constructor: 雷思flutter: didUpdateWidget:小芙出现了flutter: build:小芙flutter: build:雷思flutter: deactivate:小芙的情绪恢复了flutter: dispose:小芙将雷思从关注列表移除了
复制代码


  • deactivate:组件从组件树移除时会被调用,当然有时候组件被移除后可能被重新插入到组件的别的位置,这时候会调用 build 方法重新构建组件树。如果完全从组件树移除,之后就会调用 dispose 销毁组件。在该方法中可以解除大部分对象的引用,从而释放资源,直到 dispose 调用后释放所有资源。

  • dispose:当有状态组件再也不会调用 build 时调用该方法(通常是退出页面),在这里可以消除一些引用对象(如定时器,尚未结束的动画)来释放资源。下图展示了有状态组件的状态切换过程。


stateDiagram-v2[*] --> ConstructorConstructor --> createState
createState --> didChangeDependenciesdidChangeDependencies --> buildbuild --> deactivate: 退出build-->build: setStatebuild-->reassemble: 热重载reassemble-->重新构建(constructor)重新构建(constructor)-->didUpdateWidgetdidUpdateWidget-->builddeactivate --> disposedispose --> [*]
复制代码


从有状态和无状态组件的对比来看,有状态组件要维护的生命周期函数多好几个,性能上自然会消耗更多资源,因此如果没有必要,推荐尽量使用无状态组件。


发布于: 刚刚阅读数: 3
用户头像

岛上码农

关注

用代码连接孤岛,公众号@岛上码农 2022.03.03 加入

从南漂到北,从北漂到南的业余码农

评论

发布
暂无评论
从相亲来看Flutter 的 StatefulWidget 和 StatelessWidget_flutter_岛上码农_InfoQ写作社区