0%

Flutter探索 - 状态管理分析

状态管理是什么

我们知道最基本的程序是什么

  • 程序 = 算法 + 数据结构

    数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿整个程序世界,首先两者表现为不可分割的关系。flutter也是一个程序。由此得出

  • Flutter = 算法 + 数据结构

    那状态管理是什么?我们也用公式表示一下:

  • Flutter状态管理 = 算法 + 数据结构 + UI绑定

    来看一段代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ThemeBloc {
final _themeStreamController = StreamController<AppTheme>();
get changeTheTheme => _themeStreamController.sink.add;
get darkThemIsEnABLE => _themeStreamController.stream;
dispose(){
_themStreamController.close();
}
}
final bloc = ThemeBloc();

class AppTheme {
ThemeData themeData;
AppTheme(this.themeData);
}
//绑定到UI
StreamBulder<AppTheme>(
initialData:AppTheme.LIGHT_THEME,
stream:bloc.dartThemeIsEnabled,
builder:(contenxt,AsyncSnapshot<AppTheme> snapshot){
return MaterialApp(
title:"Jason",
theme:snapshot.data.themeData,
home:PageHome(),
routes:<String,WidgetBuilder>{
"/pageChatGroup":(context) => PageChatGroup(),
"/laoYu":(context) => LaoYu(),
}
);
}
)
  • AppTheme 是数据结构
  • changeTheTheme 是算法
  • StreamBuilder 是绑定UI

这样一整套代码的逻辑就是我们所说的Flutter状态管理。算法就是我们如何管理,数据结构是数据状态,状态管理的本质还是如何通过合理的算法管理数据,如何取,如何接收等,最终展示在UI上,通过UI的变更来提现状态的管理逻辑。

为什么需要状态管理

状态管理可以实现组件通信、跨组件数据储存。原生的状态变更是通过具体的组件直接复制,如果页面全部变更,是不是需要每一个都设置一遍,而Flutter的变更就简单粗暴 ,setState搞定,它背后的逻辑是重新build整个页面,发现有变更,再将新的数据复制。其实原生开发与Flutter的本质的区别就是数据与视图完全分离,目前原生也出现了UI绑定框架。说明flutter的设计的先进性。这样设计的弊端是什么?

答案就是页面如何刷新才是Flutter的关键,原生也面临这个问题,页面的重绘导致丢帧问题,为了效果更好,我们很多时候都选择局部刷新,原生已经很明确的告诉UI要刷新什么更新什么,而对于Flutter来说,这一点恨不清晰,虽然Flutter也做了类似虚拟Dom优化重绘逻辑,但这远远不够,如何合理的更新UI才是最主要的,这个时候一大堆的状态管理就出来了,当然状态管理也不是仅仅为了解决更新问题。我们再思考一个问题,如果我有一个widget A,我想在另外一个widget B中改变widget A的一个状态,或者从网络、数据库取到数据,然后刷新它。

一个糟糕的写法是,A widget的 state为全局的,当B widget 需要刷新的时候 直接调用 AState对象的 setState 方法.

这样写有几个问题:

1、违背了封装的概念,其他所有类都可以拿到A的state对象。

2、A和B强耦合了。

3、每次都是重绘整个widget A 性能变差。

4、不利于测试。

如何变好了,这就需要我们选择一种合适的状态管理方式。

状态管理的目标

其实我们做状态管理,不仅仅是因为它的特点,也是更好地架构。

  • 代码层次分明,易维护,易阅读。
  • 可扩展,可以动态替换UI而不影响算法逻辑。
  • 安全可靠,保持数据的稳定伸缩
  • 性能佳,局部刷新。

这不仅仅是状态管理的目的,也是我们做一款优秀应用的基础架构。

状态管理的基本分类

  • 局部管理 官方也称Ephemeral State,意思是短暂的状态,这种状态根本不需要做全局处理,StatefulWidget处理即可完成。
  • 全局管理 官方称App state ,即应用状态,非短暂状态,你要在应用程序的许多部分之间共享,以及希望在用户会话之间保持的状态,就是我们所说的应用程序状态(也称共享状态)。例如:
    • 用户偏好
    • 登录信息
    • 购物车
    • 新闻阅读状态

总之,任何Flutter应用程序中都有两种概念性的状态类型。临时状态可以使用State和setState来事项,并且通常是单个窗口小部件的本地状态。剩下的就是应用的状态。两种类型在任何Flutter应用程序中都有自己的位置,两者之间的划分取决于你自己的喜好和应用程序的复杂性。没有最好的管理方式,只有最合适的管理方式。

底层逻辑

底层逻辑主要讲,Flutter中目前有哪些可以做到状态管理,有什么缺点,适合做什么不适合做什么,完全明白底层逻辑才不会畏惧复杂的逻辑,即使逻辑复杂也能选择合理的方式去管理状态。

  • State

    StatefulWidget、SteamBuilder状态管理方式。

  • InheritedWidget

    专门负责Widget树种数据共享的功能型Widget,如Provider、scoped_model就是基于它开发

  • Notification

    与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但两者都是在自己的Widget树种传递,无法跨越树传递。

  • Stream

    数据流 如Bloc、flutter_redux、fish_redux等也都基于它来做实现。

下面一一分析

State

State是我们常用而且使用最频繁的一个状态管理类,它必须结合StatefulWidget一起使用,StreamBuilder继承自StatefulWidget,同样使用setState来管理状态。

为什么用State管理状态,而不是Widget本身。Flutter设计时让Widget本身是不变的,类似固定的配置信息,那么就需要一个角色来控制它,State就出现了,但State的任何更改都会强制整个Widget重新构建,当然也可以覆盖必要的方法自己控制逻辑。

注意:setState是整个Widget重新构建(子Widget也会销毁重建),这个点也是为什么不推荐大量使用StatefulWidget的原因。如果页面足够复杂,就会导致严重的性能损耗。建议使用StreamBuilder,它原理上是State,但它做到了子Widget的局部刷新,不会导致整个页面的重建。

State的缺点

  • 无法做到夸组件共享数据(这个夸是无关联的,如果是直接的父子关系,不算是跨组件)。一般我们将State的子类设置为私有,所以无法做到让别的组件调用State的setState函数来刷新。
  • setState会成为维护的难点,因为到处都有。随着页面状态的增多,可能调用的setState的地方会越来越多,不能统一管理。
  • 处理数据逻辑和视图混合在一起,违反代码设计原则。比如网络的数据取出来setState的UI上,这样编写代码,导致状态和UI耦合在一起,不利于测试和复用。

小结

反过来讲,State简单高效,当复杂到需要更好地管理的时候再重构,一个基本原则就是,状态是否需要跨组件使用,如果需要那就用别的方法管理状态而不是State管理。

InheritedWidget

InheritedWidget是一个无私的Widget,它可以把自己的状态数据,无私交给所有的子Widget,所有的子Widget可以无条件的继承它的状态。它的数据是只读的,子widget不能修改。

小结

缺点:

  • 容易造成不必要的刷新。
  • 不支持跨页面(router)的状态,不是同一个树的状态无法获取。
  • 数据不可变,必须结合StatefulWidget、ChangeNotifier或者Steam使用。

比较适合在一个属性Widget中,抽象出公有状态,每一个子Widget或者孙Widget都可以获取该状态,我们可以通过手段控制rebuild的粒度来优化重绘逻辑,但它更适合从上往下传递,如果从下往上传递,如何做,下面解答。

Notification

它是flutter中跨层数据共享的一种机制,它不是widget,它提供了dispatch方法,来让我们沿着context对应的Element节点向上逐层发送通知。

小结:

缺点

  • 不支持跨页面的状态,准确的说不支持NotificationListener同级或者父级Widget的状态通知
  • 本身不支持刷新UI,需要结合State使用
  • 如果结合State,会导致整个UI的重绘,效率低下不科学。

使用起来简单,如果页面复杂度高不推荐。

Stream

Stream其实是一个生产者消费者模型,一端负责生产,一端负责消费,而且是纯Dart的实现,跟Flutter没什么关系,扯上关系的就是StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、Bloc、flutter_redux全都用到了Stream的api。所以学习它才是我们掌握状态管理的关键。

小结

缺点

  • api生涩,不好理解。
  • 需要定制化,才能满足更复杂的场景。
  • 没有自动dispose逻辑。

总结

以上所有的状态更新都离不开State的支持。

状态管理的使用原则

局部管理优于全局

这个原则来源于,Flutter的性能优化,局部刷新肯定比全局刷新要好很多,那么我们在管理状态的同时,也要考虑该状态到底是局部还是全局,从而编写正确的逻辑。

保持数据安全性

_私有化状态,因为当开发人员众多,当别人看到你的变量的时候,第一反应可能不是找你提供的方法,而是直接对变量操作,那就有可能出现想不到的后果,如果他只能调用你提供的方法,那他就要遵循你方法的逻辑,避免数据被处理错误。

考虑页面重新build带来的影响

很多时候页面的重建都会调用build函数,也就是说,在一个生命周期内,build函数是多次被调用的,所以你就要考虑数据的初始化或者刷新怎么样才能合理。