写点什么

[老孟 Flutter] Stateful 组件的生命周期

用户头像
老孟Flutter
关注
发布于: 2021 年 03 月 23 日

此篇文章介绍 StatefulWidget 组件的生命周期, StatefulWidget 组件的生命周期时非常重要的知识点,就像 Android 中 Activity 的生命周期一样,不仅在以后的工作中经常用到,面试也会经常被问到。


在 Flutter 中一切皆 组件,而组件又分为 StatefulWidget(有状态)StatelessWidget(无状态)组件 ,他们之间的区别是 StatelessWidget 组件发生变化时必须重新创建新的实例,而 StatefulWidget 组件则可以直接改变当前组件的状态而无需重新创建新的实例。


注意:使用的 Flutter 版本 和 Dart 版本如下:

>

Flutter 1.22.4 • channel stable • https://github.com/flutter/flutter.git

Framework • revision 1aafb3a8b9 (6 weeks ago) • 2020-11-13 09:59:28 -0800

Engine • revision 2c956a31c0

Tools • Dart 2.10.4

>

不同的版本 StatefulWidget 组件的生命周期会有差异。


下面的 StatefulWidget 和 State 结构图是 StatefulWidget 组件生命周期的概览,不同版本的差异也可以对比此结构图。



生命周期流程图:



下面详细介绍 StatefulWidget 组件的生命周期。


生命周期一:createState


下面是一个非常简单的 StatefulWidget 组件:


class StatefulWidgetDemo extends StatefulWidget {  @override  _StatefulWidgetDemoState createState() => _StatefulWidgetDemoState();}
class _StatefulWidgetDemoState extends State<StatefulWidgetDemo> { @override Widget build(BuildContext context) { return Container(); }}
复制代码


当我们构建一个 StatefulWidget 组件时,首先执行其构造函数(上面的代码没有显示的构造函数,但有默认的无参构造函数),然后执行 createState 函数。但构造函数并不是生命周期的一部分。


当 StatefulWidget 组件插入到组件树中时 createState 函数由 Framework 调用,此函数在树中给定的位置为此组件创建 State,如果在组件树的不同位置都插入了此组件,即创建了多个此组件,如下:


Row(children: [  MyStatefulWidget(),  MyStatefulWidget(),  MyStatefulWidget(),],)
复制代码


那么系统会为每一个组件创建一个单独的 State,当组件从组件树中移除,然后重新插入到组件树中时, createState 函数将会被调用创建一个新的 State


createState 函数执行完毕后表示当前组件已经在组件树中,此时有一个非常重要的属性 mountedFramework 设置为 true


生命周期二:initState


initState 函数在组件被插入树中时被 Framework 调用(在 createState 之后),此函数只会被调用一次,子类通常会重写此方法,在其中进行初始化操作,比如加载网络数据,重写此方法时一定要调用 super.initState(),如下:


@overridevoid initState() {  super.initState();  //初始化...}
复制代码


如果此组件需要订阅通知,比如 ChangeNotifier 或者 Stream,则需要在不同的生命周期内正确处理订阅和取消订阅通知。


  • initState 中订阅通知。

  • didUpdateWidget 中,如果需要替换旧组件,则在旧对象中取消订阅,并在新对象中订阅通知。

  • 并在 dispose 中取消订阅。


另外在此函数中不能调用 BuildContext.dependOnInheritedWidgetOfExactType,典型的错误写法如下:


@overridevoid initState() {  super.initState();  IconTheme iconTheme = context.dependOnInheritedWidgetOfExactType<IconTheme>();}
复制代码


异常信息如下:



解决方案:


@overridevoid didChangeDependencies() {  super.didChangeDependencies();  context.dependOnInheritedWidgetOfExactType<IconTheme>();}
复制代码


上面的用法作为初学者使用的比较少,但下面的错误代码大部分应该都写过:


@overridevoid initState() {  super.initState();  showDialog(context: context,builder: (context){    return AlertDialog();  });}
复制代码


异常信息如下:



解决方案:


@overridevoid initState() {  super.initState();  WidgetsBinding.instance.addPostFrameCallback((timeStamp) {    showDialog(context: context,builder: (context){      return AlertDialog(title: Text('AlertDialog'),);    });  });}
复制代码


注意:弹出 AlertDialog 在 didChangeDependencies 中调用也会出现异常,但和上面的异常不是同一个。


生命周期三:didChangeDependencies


didChangeDependencies 方法在 initState 之后由 Framework 立即调用。另外,当此 State 对象的依赖项更改时被调用,比如其所依赖的 InheritedWidget 发生变化时, Framework 会调用此方法通知组件发生变化。


此方法是生命周期中第一个可以使用 BuildContext.dependOnInheritedWidgetOfExactType 的方法,此方法很少会被重写,因为 Framework 会在依赖发生变化时调用 build,需要重写此方法的场景是:依赖发生变化时需要做一些耗时任务,比如网络请求数据。


didChangeDependencies 方法调用后,组件的状态变为 dirty,立即调用 build 方法。


生命周期四:build


此方法是我们最熟悉的,在方法中创建各种组件,绘制到屏幕上。 Framework 会在多种情况下调用此方法:


  • 调用 initState 方法后。

  • 调用 didUpdateWidget 方法后。

  • 收到对 setState 的调用后。

  • State 对象的依存关系发生更改后(例如,依赖的 InheritedWidget 发生了更改)。

  • 调用 deactivate 之后,然后将 State 对象重新插入树的另一个位置。


此方法可以在每一帧中调用,此方法中应该只包含构建组件的代码,不应该包含其他额外的功能,尤其是耗时任务。


生命周期五:didUpdateWidget


当组件的 configuration 发生变化时调用此函数,当父组件使用相同的 runtimeTypeWidget.key 重新构建一个新的组件时,Framework 将更新此 State 对象的组件属性以引用新的组件,然后使用先前的组件作为参数调用此方法。


@overridevoid didUpdateWidget(covariant StatefulLifecycle oldWidget) {  super.didUpdateWidget(oldWidget);  print('didUpdateWidget');}
复制代码


此方法中通常会用当前组件与前组件进行对比。Framework 调用完此方法后,会将组件设置为 dirty 状态,然后调用 build 方法,因此无需在此方法中调用 setState 方法。


生命周期六:deactivate


当框架从树中移除此 State 对象时将会调用此方法,在某些情况下,框架将重新插入 State 对象到树的其他位置(例如,如果包含该树的子树 State 对象从树中的一个位置移植到另一位置),框架将会调用 build 方法来提供 State 对象适应其在树中的新位置。


生命周期七:dispose


当框架从树中永久移除此 State 对象时将会调用此方法,与 deactivate 的区别是,deactivate 还可以重新插入到树中,而 dispose 表示此 State 对象永远不会在 build。调用完 dispose 后,*mounted* 属性被设置为 false,也代表组件生命周期的结束,此时再调用 setState 方法将会抛出异常。


子类重写此方法,释放相关资源,比如动画等。


非常重要的几个概念


下面介绍几个非常重要的概念和方法,这些并不是生命周期的一部分,但是生命周期过程中的产物,与生命周期关系非常紧密。


mounted


mounted 是 State 对象中的一个属性,此属性表示当前组件是否在树中,在创建 State 之后,调用 initState 之前,Framework 会将 StateBuildContext 进行关联,当 Framework 调用 dispose 时,mounted 被设置为 false,表示当前组件已经不在树中。


createState 函数执行完毕后表示当前组件已经在组件树中,属性 mountedFramework 设置为 true,平时写代码时或者看其他开源代码时经常看到如下代码:


if(mounted){  setState(() {    ...  });}
复制代码


强烈建议:在调用 setState 时加上 mounted 判断。


为什么要加上如此判断?因为如果当前组件未插入到树中或者已经从树中移除时,调用 setState 会抛出异常,加上 mounted 判断,则表示当前组件在树中。


dirty 和 clean


dirty 表示组件当前的状态为 脏状态,下一帧时将会执行 build 函数,调用 setState 方法或者 执行 didUpdateWidget 方法后,组件的状态为 dirty


cleandirty 相对应,clean 表示组件当前的状态为 干净状态,*clean* 状态下组件不会执行 build 函数。


setState


setState 方法是开发者经常调用的方法,此方法调用后,组件的状态变为 dirty,当有数据要更新时,调用此方法。


reassemble


reassemble 用于开发,比如 hot reload ,在 release 版本中不会回调此方法。


交流


博客中领取 《330 个控件大全》和 《Flutter 实战》PDF。地址:http://laomengit.com


发布于: 2021 年 03 月 23 日阅读数: 9
用户头像

老孟Flutter

关注

公众号老孟Flutter 《Flutter 实战入门》。 2021.01.11 加入

还未添加个人简介

评论

发布
暂无评论
[老孟Flutter] Stateful 组件的生命周期