Flutter 教程(三)状态管理

声明式和命令式构建UI的区别

在上一篇文章 Flutter 教程(二)Flutter 组件中,我们了解了在 Flutter 中构建UI的方式。可以看到,它们和在 Android 传统构建 UI 的方式完全不同。这是因为 Flutter 是基于声明式构建UI的,而在 Android ,是命令式构建UI的。

以 Flutter 默认的计数器例子为例:

在 Android 中,实现上面的效果的代码如下所示:

csharp 复制代码
// 一、定义展示的内容
private int mCount = 0;
 
// 二、中间展示数字的控件 TextView
private TextView mTvCount;
 
// 三、关联 TextView 与 xml 中的组件
mTvCount = findViewById(R.id.tv_count)
 
// 四、点击按钮控制组件更新
private void increase( ){ 
	mCount++;
	mTvCounter.setText(mCount.toString()); 
}

可以看到在Android中实现计数更新的功能,需要获取对应的UI控件对象,然后设置该对象的值。而在 Flutter 中,声明 UI 布局之后,只需要通过 setState 来刷新对应的界面即可,而不需要进行繁琐的控制,代码示例如下:

scss 复制代码
// 一、声明变量
int _counter =0; 

// 二、展示变量 
Text('$_counter')

//  三、变量增加,更新界面
setState(() {
   _counter++; 
});

声明式开发的优势

让开发者摆脱组件的繁琐控制,聚焦于状态处理

在开发 Android 原生时,你会发现当多个组件之间相互关联时,对于 View 的控制非常麻烦。而在 Flutter 中我们只需要处理好状态即可 (复杂度转移到了状态 -> UI 的映射,也就是 Widget 的构建)。

声明式开发的劣势

使用声明式开发主要遇到的问题有三个:

  1. 逻辑和页面 UI 耦合,导致无法复用/单元测试、修改混乱等
  2. 难以跨组件 (跨页面) 访问数据
  3. 无法轻松的控制刷新范围 (页面 setState 的变化会导致全局页面的变化)

逻辑和页面 UI 耦合

一开始业务不复杂的时候,所有的代码都直接写到 widget 中,随着业务迭代,文件越来越大,其他开发者很难直观地明白里面的业务逻辑。这就导致了逻辑和页面 UI 耦合,导致无法复用/单元测试、修改混乱等问题。

这个问题在 Android 原生上同样存在,现在一般 MVP、MVVM、MVI等设计模式的思路去解决。

难以跨组件 (跨页面) 访问数据

如上图所示,在 Widget 结构中,一个子组件想要展示父组件中的 name 字段,可能需要层层进行传递。又或者是要在两个页面之间共享筛选数据,并没有一个很优雅的机制去解决这种跨页面的数据访问。

无法轻松的控制刷新范围

setState 会触发对你当前所在的小组件的重建。如果你的整个应用程序只包含一个widget,那么整个widget将被重建,这将使你的应用程序变得缓慢。

为什么需要状态管理

前面我们提到了声明式UI会造成的三个问题,而状态管理的目的就是解决「声明式」开发带来的问题。 Flutter 中常用的状态管理框架有 GetProvider

这两个框架各有优缺点,如果你或者你的团队刚接触 Flutter,使用 Provider 能帮助你们更快理解 Flutter 的核心机制。而如果已经对 Flutter 的原理有了解,Get 丰富的功能和简洁的 API,则能帮助你很好地提高开发效率。

组件基础知识

在介绍 Provider 和 Get 状态管理框架前,我们需要先了解一下组件的基础知识。这样才方便理解状态管理实现的原理。

Flutter 三棵树

Flutter 的渲染是通过三棵树实现的,三棵树分别为:

  • Widget 树:Widget是Flutter的核心部分,它的定义就是 对一个 Element 配置的描述,也就是说,widget 只是一个配置的描述,并不是真正的渲染对象,就相当于是 Android 里面的 xml,只是描述了一下属性,但他并不是真正的 View。
  • Element 树:Element 是实例化的 Widget 对象,通过 Widget 的 createElement() 方法,是在特定位置使用 Widget配置数据生成;
  • RenderObject 树:用于应用界面的布局和绘制,保存了元素的大小,布局等信息;

StatelessWidget 和 StatefulWidget

在Flutter开发中,一切皆组件,我们展示给用户的界面也是一个组件。而组件 Widget 主要被划分为 StatelessWidget 和 StatefulWidget 两大类。StatelessWidget 就是一个无状态组件。由 StatelessWidget 设计出来的界面内容是无法使用setState()方法改变的。StatelessWidget 的代码示例如下:

scala 复制代码
class MyStateLessWidget extends StatelessWidget{
  final String title;
  MyStateLessWidget({
    Key key,
    this.title,
  });
  @override
  Widget build(BuildContext context){
    return new ...;
  }
}

而StatefulWidget 是有状态组件。当创建一个StatefulWidget组件的时候,肯定也会创建一个State对象。通过这个对象,我们可以与用户交互并刷新界面。StatefulWidget 的代码示例如下:

scala 复制代码
// 主体部分
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
// State 部分
class _MyHomePageState extends State<MyHomePage> {
  ...
  @override
  Widget build(BuildContext context) {
     return new ...;
  }
}

我们需要改变StatefulWidget组件的界面内容,就需要使用setState(...),这个服务由Flutter框架层控制来更新UI。

BuildContext

从上面的代码示例,可以看到在实现 StatelessWidget 和 StatefulWidget 来创建组件时,会使用到 BuildContext。而 BuildContext 就是 widget 对应的 Element。BuildContext 的代码示例如下:

scss 复制代码
Theme.of(context) //获取主题
Navigator.push(context, route) //入栈新路由
Localizations.of(context, type) //获取Local
context.size //获取上下文大小
context.findRenderObject() //查找当前或最近的一个祖先RenderObject

为什么不直接传入 Element,而是传入 BuildContext;这是因为 BuildContext 是 Element 的接口,传入 BuildContext 可以限制直接操作 Element

State 生命周期

我们将State的生命周期分为3个部分:第一部分是启动 App 的运行流程;第二部分是热重载的运行流程;第三部分是界面销毁时的运行流程。

State的生命周期中的几个非常重要的方法如下:

  • initState。initState()方法是State的生命周期中创建运行的第一个方法。在Flutter项目中,如果需要初始化一些数据,或者绑定控制器(controller),就可以重写在这个方法中。需要注意的是,要重写该方法,就必须在该方法中加上super.initState()。不过,在initState()方法里,Flutter框架层还没有把Context与State关联在一起。因此,不能在这个方法中访问Context。另外,initState()方法在生命周期中只会运行一次。
  • didChangeDependencies。didChangeDependencies()方法在State对象的依赖发生变化时被调用,在initState()方法之后运行,这个时候,就可以访问Context了。如果之前build()中包含一个InheritedWidget,而之后的Widget使用了InheritedWidget数据,并且发生了变化,那么Flutter框架层就会调用didChangeDependencies方法
  • build。build()主要是用于构建Widget树,它在didChangeDependencies()和didUpdateWidget()之后被运行。基本上每次调用setState()都会运行build()方法。
  • reassemble。reassemble()回调方法是专门为了开发调试而提供的,在热重载时会被调用。此回调方法在Release模式下永远不会被调用。
  • didUpdateWidget。祖先节点重新构建Widget时会调用didUpdateWidget()方法,当组件的状态改变的时候也会调用didUpdateWidget()方法。需要注意的是,运行setState()方法并不会调用didUpdateWidget()方法,反而热重载的时候才会调用。这个方法一般用于监测新、旧Widget的属性,看看哪些属性值改变了,并对State做一些调整。
  • deactivate。在dispose()方法之前会调用这个方法。实测在组件可见状态变化的时候会调用deactivate()方法,当组件卸载时也会先一步在dispose()前调用deactivate()。
  • dispose。当State对象从树中被永久移除时会调用dispose()方法,通常在此回调方法中释放资源。一旦到这个阶段,组件就要被销毁了。这个方法一般用于移除监听,清理环境。

Key

在实现 StatelessWidget 和 StatefulWidget 时,都会使用一个 Key。它代表 Widget 的唯一标识。这个唯一标识在 build/rendering 阶段由框架定义。

关于 Key 的详情可以看 Flutter | Key 的原理和使用概述

InheritedWidget

InheritedWidget 是 Flutter 提供的一种在 widget 树中从上到下共享数据的方式,即在父widget 中通过InheritedWidget共享了一个数据,那么在任意子widget都能获取该共享的数据。

前面提到的 Provider 框架就是根据 InheritedWidget 来实现状态管理的

Provider 的使用

Provider 的使用具体看Flutter状态管理之 Provider 使用详解

GetX 的使用

GetX 的使用具体看Flutter GetX使用

参考

相关推荐
火炎焱燚-15 小时前
Flutter - 基础Widget
flutter
ClaNNEd@16 小时前
001第一个flutter文件
前端·flutter
江上清风山间明月16 小时前
一周掌握Flutter开发--4、导航与路由
android·flutter·路由·导航·ongenerateroute·navigator.push·navigator.pop
l软件定制开发工作室18 小时前
Flutter系列教程之(4)——自定义Widget控件及相关知识
flutter
DeMonnnnnn21 小时前
Flutter-升级Xcode后构建iOS报错
flutter·ios·xcode
pengyu1 天前
系统化掌握Flutter开发之Stack:布局系统中的"瑞士军刀”
android·flutter·dart
火炎焱燚-2 天前
Flutter - StatefulWidget (有状态的 Widget) 和 生命周期
flutter
l软件定制开发工作室2 天前
Flutter系列教程之(2)——Dart语言快速入门
flutter
Python私教2 天前
Flutter屏幕适配终极方案:flutter_screenutil深度解析
flutter