Flutter说:详解生命周期、状态管理及局部重绘(上)

简介: 生命周期flutter的生命周期其实有两种:StatefulWidget和StatelessWidget。这两个是flutter的两个基本组件,名称已经很好表明了这两个组件的功能:有状态和无状态。

生命周期


flutter的生命周期其实有两种:StatefulWidget和StatelessWidget。

这两个是flutter的两个基本组件,名称已经很好表明了这两个组件的功能:有状态和无状态。


(1)StatelessWidget


StatelessWidget是无状态组件,它的生命周期非常简单,只有一个build,如下:

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ...;
  }
}
复制代码


对于StatelessWidget来说只渲染一次,之后它就不再有任何改变。

由于无状态组件在执行过程中只有一个 build 阶段,在执行期间只会执行一个 build 函数,没有其他生命周期函数,因此在执行速度和效率方面比有状态组件更好。所以在设计组件时,要考虑业务情况,尽量使用无状态组件。


(2)StatefulWidget


StatelessWidget是有状态组件,我们讨论的生命周期也基本指它的周期,如图:

网络异常,图片无法展示
|


包含以下几个阶段:

  • createState:该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。
  • initState:该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。
  • didChangeDependencies:该函数是在该组件依赖的 State 发生变化时,这里说的 State 为全局 State ,例如语言或者主题等,类似于前端 Redux 存储的 State 。
  • build:主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常,注意这里的性能问题
  • reassemble:主要是提供开发阶段使用,在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。
  • didUpdateWidget:该函数主要是在组件重新构建,比如说热重载,父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。
  • deactivate:在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。
  • dispose:永久移除组件,并释放组件资源。

在StatelessWidget中,只要我们调用setState,就会执行重绘,也就是说重新执行build函数,这样就可以改变ui。


State改变时组件如何刷新


先来看看下方的代码:


class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(_counter),
            WidgetB(),
            WidgetC(_incrementCounter)
          ],
        ),
      ),
    );
  }
}
class WidgetA extends StatelessWidget {
  final int counter;
  WidgetA(this.counter);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(counter.toString()),
    );
  }
}
class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('I am a widget that will not be rebuilt.');
  }
}
class WidgetC extends StatelessWidget {
  final void Function() incrementCounter;
  WidgetC(this.incrementCounter);
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        incrementCounter();
      },
      child: Icon(Icons.add),
    );
  }
}
复制代码


我们有三个Widget,一个负责显示count,一个按钮改变count,一个则是静态显示文字,通过这三个Widget来对比比较页面的刷新逻辑。

上面代码中,三个Widget是在_MyHomePageState的build中创建的,执行后点击按钮可以发现三个Widget都刷新了

在Flutter Performance面板上选中Track Widget Rebuilds即可看到


网络异常,图片无法展示
|


虽然三个Widget都是无状态的StatelessWidget,但是因为_MyHomePageState的State改变时会重新执行build函数,所以三个Widget会重新创建,这也是为什么WidgetA虽然是无状态的StatelessWidget却依然可以动态改变的原因。

所以:无状态的StatelessWidget并不是不能动态改变,只是在其内部无法通过State改变,但是其父Widget的State改变时可以改变其构造参数使其改变。实际上确实不能改变,因为是一个新的实例。


下面我们将三个组件提前创建,可以在_MyHomePageState的构造函数中创建,修改后

代码如下:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  List<Widget> children;
  _MyHomePageState(){
    children = [
      WidgetA(_counter),
      WidgetB(),
      WidgetC(_incrementCounter)
    ];
  }
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: children,
        ),
      ),
    );
  }
}
复制代码


再次执行,发现点击没有任何效果,Flutter Performance上可以看到没有Widget刷新(这里指三个Widget,当然Scaffold还是刷新了)。

这是因为组件都提前创建了,所以执行build时没有重新创建三个Widget,所以WidgetA显示的内容并没有改变,因为它的counter没有重新传入。

所以,不需要动态改变的组件可以提前创建,build时直接使用即可,而需要动态改变的组件实时创建。

这样就可以实现局部刷新了么?我们继续改动代码如下:


class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Widget b = WidgetB();
  Widget c ;
  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
  }
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(_counter),
            b,
            c
          ],
        ),
      ),
    );
  }
}
复制代码


我们只将WidgetB和WidgetC重新创建,而WidgetA则在build中创建。执行后,点击按钮WidgetA的内容改变了,查看Flutter Performance可以看到只有WidgetA刷新了,WidgetB和WidgetC没有刷新。


网络异常,图片无法展示
|


所以:通过提前创建静态组件build时直接使用,而build时直接创建动态Widget 这种方式可以实现局部刷新。

注意:

只要setState,_MyHomePageState就会刷新,所以WidgetA就会跟着刷新,即使count没有改变。比如上面代码中将setState中的_count++代码注释掉,再点击按钮虽然内容没有改变,但是WidgetA依然刷新。

这种情况可以通过InheritedWidget来进行优化。


InheritedWidget


InheritedWidget的作用什么?网上有人说是数据共享,有人说是用于局部刷新。我们看官方的描述:

Base class for widgets that efficiently propagate information down the tree.

可以看到它的作用是Widget树从上到下有效的传递消息,所以很多人理解为数据共享,但是注意这个“有效的”,这个才是它的关键,而这个有效的其实就是解决上面提到的问题。

那么它怎么使用?

先创建一个继承至InheritedWidget的类:


class MyInheriteWidget extends InheritedWidget{
  final int count;
  MyInheriteWidget({@required this.count, Widget child}) : super(child: child);
  static MyInheriteWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyInheriteWidget>();
  }
  @override
  bool updateShouldNotify(MyInheriteWidget oldWidget) {
    return oldWidget.count != count;
  }
}
复制代码


这里将count传入。重点注意要实现updateShouldNotify函数,通过名字可以知道这个函数决定InheritedWidget的Child Widget是否需要刷新,这里我们判断如果与之前改变了才刷新。这样就解决了上面提到的问题。

然后还要实现一个static的of方法,用于Child Widget中获取这个InheritedWidget,这样就可以访问它的count属性了,这就是消息传递,即所谓的数据共享(因为InheritedWidget的child可以是一个layout,里面有多个widget,这些widget都可以使用这个InheritedWidget中的数据)。

然后我们改造一下WidgetA:


class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheriteWidget myInheriteWidget = MyInheriteWidget.of(context);
    return Center(
      child: Text(myInheriteWidget.count.toString()),
    );
  }
}
复制代码


这次不用在构造函数中传递count了,直接通过of获取MyInheriteWidget,使用它的count即可。

最后修改_MyHomePageState:


class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Widget a = WidgetA();
  Widget b = WidgetB();
  Widget c ;
  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
  }
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteWidget(
              count: _counter,
              child: a,
            ),
            b,
            c
          ],
        ),
      ),
    );
  }
}
复制代码


注意,这里用MyInheriteWidget包装一下WidgetA,而且WidgetA必须提前创建,如果在build中创建则每次MyInheriteWidget刷新都会跟着刷新,这样updateShouldNotify函数的效果就无法达到。

执行,点击按钮,可以发现只有WidgetA刷新了(当然MyInheriteWidget也刷新了)。如果注释掉setState中的_count++代码,再执行并点击发现虽然MyInheriteWidget刷新了,但是WidgetA并不刷新,因为MyInheriteWidget的count并未改变。

下面我们改动一下代码,将WidgetB和C都放入MyInheriteWidget会怎样?


@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteWidget(
              count: _counter,
              child: Column(
                children: [
                  a,
                  b,
                  c
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
复制代码


MyInheriteWidget的child是一个Column,将a、b、c都放在这下面。执行会发现依然是WidgetA刷新,B和C都不刷新。这是因为在B和C中没有执行MyInheriteWidget的of函数,就没有执行dependOnInheritedWidgetOfExactType,这样其实就没构成依赖,MyInheriteWidget就不会通知它们。

如果我们修改WidgetC,在build函数中添加一行MyInheriteWidget.of(context);那么虽然没有任何使用,依然能会跟着刷新,因为建立了依赖关系就会被通知。

InheritedWidget会解决多余的刷新问题,比如在一个页面中有多个属性,同样有多个Widget来使用这些属性,但是并不是每个Widget都使用所有属性。如果用最普通的实现方式,那么每次setState(无论改变哪个属性)都需要刷新这些Widget。但是如果我们用多个InheritedWidget来为这些Widget分类,使用相同属性的用同一个InheritedWidget来包装,并实现updateShouldNotify,这样当改变其中一个属性时,只有该属性相关的InheritedWidget才会刷新它的child,这样就提高了性能。

目录
相关文章
|
26天前
|
存储 JavaScript 前端开发
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战。本文介绍了几种常用的状态管理框架,如Provider和Redux,分析了它们的基本原理、优缺点及适用场景,并提供了选择框架的建议和使用实例,旨在帮助开发者提高开发效率和应用性能。
34 4
|
1月前
|
存储 Shell 开发工具
Flutter&鸿蒙next 中使用 MobX 进行状态管理
本文介绍了如何在 Flutter 中使用 MobX 进行状态管理。MobX 是一个基于观察者模式的响应式编程库,通过 `@observable` 和 `@action` 注解管理状态,并使用 `Observer` 小部件自动更新 UI。文章详细讲解了 MobX 的核心概念、如何集成到 Flutter 项目中以及具体的代码示例。适合希望在 Flutter 应用中实现高效状态管理的开发者阅读。
100 9
|
1月前
|
存储 开发者
Flutter&鸿蒙next 使用 BLoC 模式进行状态管理详解
本文详细介绍了如何在 Flutter 中使用 BLoC 模式进行状态管理。BLoC 模式通过将业务逻辑与 UI 层分离,利用 Streams 和 Sinks 实现状态管理和 UI 更新,提高代码的可维护性和可测试性。文章涵盖了 BLoC 的基本概念、实现步骤及代码示例,包括定义 Event 和 State 类、创建 Bloc 类、提供 Bloc 实例以及通过 BlocBuilder 更新 UI。通过一个简单的计数器应用示例,展示了 BLoC 模式的具体应用和代码实现。
85 1
|
1月前
|
开发工具 开发者
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
本文深入探讨了 Flutter 中 Provider 的高级用法,涵盖多 Provider 组合、Selector 优化性能、ChangeNotifierProxyProvider 管理依赖关系以及自定义 Provider。通过这些技巧,开发者可以构建高效、可维护的响应式应用。
81 2
|
2月前
|
开发工具 开发者
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
|
1月前
|
缓存 JavaScript API
Flutter&鸿蒙next 状态管理框架对比分析
在 Flutter 开发中,状态管理至关重要,直接影响应用的性能和可维护性。本文对比分析了常见的状态管理框架,包括 setState()、InheritedWidget、Provider、Riverpod、Bloc 和 GetX,详细介绍了它们的优缺点及适用场景,并提供了 Provider 的示例代码。选择合适的状态管理框架需考虑应用复杂度、团队熟悉程度和性能要求。
109 0
|
2月前
【Flutter】状态管理:Provider状态管理
【Flutter】状态管理:Provider状态管理
24 0
|
2月前
|
UED
flutter:动画&状态管理 (十三)
本文档介绍了Flutter中`animatedList`的使用方法和状态管理的示例。`animatedList`用于创建带有动画效果的列表,示例代码展示了如何添加、删除列表项,并执行相应的动画效果。状态管理部分通过一个简单的点击切换颜色的示例,演示了如何在Flutter中管理组件的状态。
|
2月前
|
开发框架 UED 计算机视觉
flutter:图片&stful 生命周期 (三)
本文档介绍了如何在Flutter中处理图片,包括加载网络图片、本地图片、创建圆形图片和带有圆角的图片,以及如何配置`pubspec.yaml`文件来添加资源文件。还展示了如何使用`AssetImage`对象来显示本地资源图片,并通过代码示例详细说明了这些操作的实现方法。最后,简要介绍了StatefulWidget的生命周期。
|
5月前
|
容器
flutter 布局管理【详解】
flutter 布局管理【详解】
45 3