Flutter 入门与实战(三十八):从相亲来看有状态和无状态组件|8月更文挑战

2,175 阅读7分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

楔子

雷思是一名程序员,年龄老大不小了,家里急啊,老催他相亲。可是雷思同学对相亲不太上心,相亲网站应付似的填了点东西,然后截个图给他老娘发过去,说是已经在相亲网站挂出去了。其实他压根就没当回事——程序员嘛,哪里会缺对象,现在用了 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: 雷思');
  }
}

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

@override
void initState() {
  super.initState(); //妈妈教导过的话
  print('initState:小芙花了2小时化妆');
}

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

相亲的开场白

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

@override
void didUpdateWidget(oldWidget) {
  super.didUpdateWidget(oldWidget);
  print('didUpdateWidget:小芙出现了');
}

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

相亲的过程

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

@override
Widget build(BuildContext context) {
  print('build:雷思');
  return Center(
    child: Text('雷思随便点了两个菜'),
  );
}
@override
Widget build(BuildContext context) {
  print('build:小芙');
  return Center(
    child: Column(children: [
      Text('表情:$_face'),
      TextButton(
          onPressed: () {
            setState(() {
              _face = '失望';
              print('小芙的表情变了');
            });
          },
          child: Text('改变表情')),
    ]),
  );
}

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

结局

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

@override
void deactivate() {
  print('deactivate:小芙的情绪平复了');
  super.deactivate();
}

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

@override
void 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
[*] --> Constructor
Constructor --> createState

createState --> didChangeDependencies
didChangeDependencies --> build
build --> deactivate: 退出
build-->build: setState
build-->reassemble: 热重载
reassemble-->重新构建(constructor)
重新构建(constructor)-->didUpdateWidget
didUpdateWidget-->build
deactivate --> dispose
dispose --> [*]

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


我是岛上码农,微信公众号同名。这是Flutter 入门与实战的专栏文章。

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!