Flutter有状态组件StatefulWidget生命周期详解

作者:半点橘色 时间:2023-09-25 23:56:50 

1、StatefulWidget的背后

flutter开发过程中,我们经常会用到两个组件StatelessWidget和StatefulWidget。前者为无状组件,后者为有状态组件,无状态组件通常在创建后内部的数据无法改变,而有状态组件可以维持内部的数据状态,适合动态组件使用。

/// 无状态组件的定义
class MyApp extends StatelessWidget {}
/// 有状态状态组件的定义
class MyApp extends StatefullWidget {
 @override
 State<StatefulWidget> createState() => _MyApp();
}
class _MyApp extends State<MyApp> {}

定义无状态组件相对简单,只需要继承StatelessWidget即可,而有状态组件需要两个类来实现,首先是继承StatefullWidget,然后重写createState方法,返回State的实现类。

刚始我不明白StatefullWidget为何要通过重写createState的方式来实现,后来通过对StatelessWidget的深入,我渐渐的理解了其中的用意。

首先,StatelessWidget和StatefulWidget都的父类来自Widget,而Widget在定义过程中使用了dart的注解@immutable。

@immutable的作用根据官方的解释:被@immutable注解标明的类或者子类都必须是不可变的。

也就是说,继承了StatelessWidget和StatefullWidget的组件都必须为常量组件,可以使用const修饰,而它的构造函数和成员属性需要是常量,不可变的。

class MyApp extends StatelessWidget {
 /// 常量属性,不加final会警告
 final String? title;
 const MyApp({Key? key, this.title}) : super(key: key);
}
/// or
class MyApp extends StatefulWidget {
 /// 常量属性,不加final会警告
 final String? title;
 const MyApp({Key? key, this.title}) : super(key: key);
 @override
 State<StatefulWidget> createState() => _MyApp();
}
/// 调用时可以加const
const MyApp()

StatelessWidget自然没什么问题,本身它就是无状态组件,创建出来内部数据不会改变,符合@immutable的定义。而StatefullWidget不同,作为有状态组件,需要维持自身的成员属性可变,不能是一个常量。那如何解决呢?就是通过State来保持状态,因为它并不继承Widget,不受@immutable的影响,内部成员可以定义成变量。

还有个问题,为什么Widget非要使用@immutable来注释?

这一切的出发点都是为了减少性能的损耗,提高Widget构建效率。因为常量定义的类,不会重复构建,可以大大提升运行速度,只要明白这一点,就能解了StatefulWidget这样设计的意义。

2、StatefulWidget的生命周期

StatefulWidget通过State来管理状态,同时也提供了相应的生命周期函数,如initState、didUpdateWidget、dispose等,我们只需要重写这些函数,StatefulWidget在执行过程中会在合适的时机调用。

Flutter有状态组件StatefulWidget生命周期详解

StatefulWidget的生命周期可以分为创建阶段、更新阶段和销毁阶段,下面我们结合一个示例来看下它的执行过程。

/// StatefulWidget demo
import 'package:flutter/material.dart';
void main() {
 runApp(const MyApp());
}
class ColorInheritedWidget extends InheritedWidget {
 final Color color;
 const ColorInheritedWidget({
   Key? key,
   required this.color,
   required Widget child,
 }) : super(key: key, child: child);
 @override
 bool updateShouldNotify(covariant ColorInheritedWidget oldWidget) {
   return oldWidget.color != color;
 }
 static ColorInheritedWidget? of(BuildContext context) =>
     context.dependOnInheritedWidgetOfExactType<ColorInheritedWidget>();
}
class MyApp extends StatefulWidget {
 final String? title;
 const MyApp({Key? key, this.title}) : super(key: key);
 @override
 State<StatefulWidget> createState() {
   print('createState');
   return _MyApp();
 }
}
class _MyApp extends State<MyApp> {
 int value = 0;
 bool isDetach = false;
 Color color = Colors.red;
 @override
 void initState() {
   super.initState();
   print('initState');
 }
 @override
 void didChangeDependencies() {
   super.didChangeDependencies();
   print('didChangeDependencies');
 }
 @override
 void didUpdateWidget(covariant MyApp oldWidget) {
   super.didUpdateWidget(oldWidget);
   print('didUpdateWidget');
 }
 @override
 void deactivate() {
   super.deactivate();
   print('deactivate');
 }
 @override
 void dispose() {
   super.dispose();
   print('deactivate');
 }
 @override
 Widget build(BuildContext context) {
   print('build');
   return ColorInheritedWidget(
     color: color,
     child: MaterialApp(
       home: Scaffold(
         appBar: AppBar(
           title: const Text('StatefulWidget demo'),
         ),
         body: Column(
           children: [
             if (!isDetach) MyAppChild(value: value),
             MaterialButton(
               color: Colors.blue,
               onPressed: () {
                 value++;
                 setState(() {});
               },
               child: const Text('更新value'),
             ),
             MaterialButton(
               color: Colors.blue,
               onPressed: () {
                 color = color == Colors.red ? Colors.yellow : Colors.red;
                 setState(() {});
               },
               child: const Text('更新color'),
             ),
             MaterialButton(
               color: Colors.blue,
               onPressed: () {
                 isDetach = !isDetach;
                 setState(() {});
               },
               child: Text(isDetach ? '添加' : '移除'),
             )
           ],
         ),
       ),
     ),
   );
 }
}
class MyAppChild extends StatefulWidget {
 final int value;
 const MyAppChild({Key? key, required this.value}) : super(key: key);
 @override
 State<StatefulWidget> createState() {
   print('child-createState');
   return _MyAppChild();
 }
}
class _MyAppChild extends State<MyAppChild> {
 @override
 void initState() {
   super.initState();
   print('child-initState');
 }
 @override
 void didChangeDependencies() {
   super.didChangeDependencies();
   print('child-didChangeDependencies');
 }
 @override
 void didUpdateWidget(covariant MyAppChild oldWidget) {
   super.didUpdateWidget(oldWidget);
   print('child-didUpdateWidget');
 }
 @override
 void deactivate() {
   super.deactivate();
   print('child-deactivate');
 }
 @override
 void dispose() {
   super.dispose();
   print('child-dispose');
 }
 @override
 Widget build(BuildContext context) {
   print('child-build');
   return Container(
     width: 100,
     height: 100,
     color: ColorInheritedWidget.of(context)?.color,
     child: Text('${widget.value}'),
   );
 }
}

Flutter有状态组件StatefulWidget生命周期详解

2.1创建阶段

运行demo,首次渲染会打印出:

flutter: createState
flutter: initState
flutter: didChangeDependencies
flutter: build
flutter: child-createState
flutter: child-initState
flutter: child-didChangeDependencies
flutter: child-build

从这里我们可以看出,StatefulWidget组件执行的顺序createState -> initState -> didChangeDependencies -> build。

  • createState

Widget只是对组件信息的描述,而Element才是最终对Widget的实现。通过Element可以进行Widget的挂载和RenderObject的创建,最终实现UI的渲染。

Flutter有状态组件StatefulWidget生命周期详解

flutter首帧渲染的过程中,会不断向下遍历Widget树,通过cteateElememt创建Element实例,每创建一个Widget都会对应创建一个Element。例如创建StatefulWidget时会对应创建StatefulElement。

abstract class StatefulWidget extends Widget {
 const StatefulWidget({ Key? key }) : super(key: key);
 @override
 StatefulElement createElement() => StatefulElement(this);
 /// ...    
}

StatefulElement的构造函数中,执行了StatefulWidget的createState方法,完成对State的调用。

class StatefulElement extends ComponentElement {
 StatefulElement(StatefulWidget widget)
     : _state = widget.createState(),
       super(widget) {
           state._element = this;
           state._widget = widget;
       }
 /// ...
}
  • initState

createState通过StatefulElement构造函数来创建,而initState在firstBuild方法中定义,firstBuild是首帧渲染的时候,通过StatefulElement实例的mount方法进行调用,firstBuild只会执行一次,也就是说initState只会在Widget首次创建次调用。

通常我们会使用initState进行数据的初始化,也可以进行网络请求操作。

/// StatefulElement
 @override
 void _firstBuild() {
   try {
     /// 调用initState
     final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
   } finally {
     /// ...
   }
   /// 调用didChangeDependencies
   state.didChangeDependencies();
   /// 调用父级ComponentElement类的_firstBuild,再往上到Element的rebuild方法,最后触发的是StatefulElement中的performRebuild方法。
   super._firstBuild();
 }
 @override
 void performRebuild() {
   /// _didChangeDependencies标记Element的依赖节点发生改变,didChangeDependencies会再次调用。
   if (_didChangeDependencies) {
     state.didChangeDependencies();
     _didChangeDependencies = false;
   }
   /// 调用ComponentElement中的performRebuild,最终触发StatefulElement的build方法
   super.performRebuild();
 }
 @override
 Widget build() => state.build(this);

2.2更新阶段

在State类中调用了setState方法,会解发组件update流程,它会对比新旧Element,将修改过的组件标记为脏元素。如果Widget依赖InheritedWidget的数据发现变化,会触发didChangeDependencies函数,接着会调用子组件的update方法,在StatefulElement的update方法中执行了didUpdateWidget,最后才执行rebuild进行build的调用,完成UI的更新。

/// StatefulElement
 @protected
 @pragma('vm:prefer-inline')
 Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
   /// ...
   final Element newChild;
   if (child != null) {
     bool hasSameSuperclass = true;
     if (hasSameSuperclass && child.widget == newWidget) {
       newChild = child;
     } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
       /// 更新逻辑会调用child的update方法
       child.update(newWidget);
       newChild = child;
     }  
   }
   return newChild;
 }
/// StatefulElement
 @override
 void update(StatefulWidget newWidget) {
   super.update(newWidget);
   try {
     /// 调用state.didUpdateWidget
     final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
   }
   /// 重新rebuild深入遍历子组件
   rebuild();
 }

在demo中,点击&ldquo;更新value&rdquo;按钮,会打印出:

flutter: build
flutter: child-didUpdateWidget
flutter: child-build

从这里可以看出父组件setState时,会先走自身的build再触发子组件的didUpdateWidget和build。

2.3销毁阶段

在demo中,点击&ldquo;移除&rdquo;按钮,会打印出:

flutter: build
flutter: child-deactivate
flutter: child-dispose

组件移除节点后会调用deactivate,如果该组件被移除节点,然后未被 插入到其他节点时,则会继续调用 dispose 永久移除,并释放组件资源。

总结:

1、StatefulWidget通过State来管理状态数据,目的是为了保持StatefulWidget可常量创建,减少性能的损耗,提高Widget构建效率。

2、StatefulWidget创建阶段生命周期先执行顺序createState -> initState -> didChangeDependencies -> build。可以在initState进行数据初始化、网络请求操作。

3、StatefulWidget更新阶段:build -> child didUpdateWidget -> child build。

4、StatefulWidget销毁阶段:build -> child deactivate -> child dispose。

来源:https://juejin.cn/post/7187607597426409533

标签:Flutter,StatefulWidget,生命周期,有状态组件
0
投稿

猜你喜欢

  • C#中逆变的实际应用场景详解

    2023-12-21 19:59:58
  • 详解java调用python的几种用法(看这篇就够了)

    2023-04-10 22:15:57
  • c# Selenium爬取数据时防止webdriver封爬虫的方法

    2023-06-24 07:50:51
  • Java实现爬取百度图片的方法分析

    2023-12-19 23:51:27
  • java面试try-with-resources问题解答

    2023-09-03 15:08:01
  • 解决IDEA鼠标点击光标变大问题

    2022-12-07 11:52:37
  • C#操作config文件的具体方法

    2023-09-03 12:18:07
  • Struts 2中实现Ajax的三种方式

    2022-04-30 05:46:28
  • 浅谈C#网络编程详解篇

    2022-10-23 04:30:28
  • Spring MVC通过添加自定义注解格式化数据的方法

    2023-11-06 09:05:32
  • Unity3D绘制地形的实现方法

    2022-12-01 01:06:46
  • C# ArrayList、HashSet、HashTable、List、Dictionary的区别详解

    2022-06-02 05:22:45
  • Android编程自定义组件实例详解

    2022-12-20 13:11:58
  • C#远程发送和接收数据流生成图片的方法

    2021-08-31 00:30:10
  • Token登陆验证机制的原理及实现

    2022-07-08 03:28:19
  • IntelliJ IDEA 如何配置git的操作方法

    2021-12-28 11:24:44
  • Java中Prime算法的原理与实现详解

    2022-06-11 23:16:29
  • C++容器适配与栈的实现及dequeque和优先级详解

    2023-11-02 12:57:52
  • 将本地jar包安装进入maven仓库(实现方法)

    2022-06-18 00:35:54
  • C#简单输出日历的方法

    2023-11-13 05:56:10
  • asp之家 软件编程 m.aspxhome.com