目
录
1.什么是状态管理
2.不同的状态管理分类
3.Flutter中的有状态组件和无状态组件
4.Flutter中有哪些可以做到状态管理
5.为什么要使用状态管理
6.常见的状态管理框架有哪些
7.状态管理总结&思考
什么是状态管理
随着“大前端”概念流行的同时,响应式编程的理念也随之被越来越多的人所了解和学习,要了解和学习响应式的编程框架就一定离不开状态管理,客户端Android、iOS是通过明确的命令式指令去控制我们的UI变化,如setText,而在响应式编程下,我们只需要描述好UI和状态之间的关系,然后专注于状态的改变就好了,框架会根据状态的变化来自动更新UI。
总结来说就是:状态管理就是当某个状态发生改变的时候,告知使用该状态的状态监听者,让状态所监听的属性随知改变,从而达到联动效果。
不同的状态管理分类
短时状态Ephemeral state
某些状态、或是可以理解为某些数据只需要在当前的Widget中访问和使用,不需要对这些状态进行共享访问,你需要的只是一个StatefulWidget组件,依靠这个StatefulWidget组件自己的State类自己管理即可,不需要使用状态管理框架去管理这种状态,这些状态可以称之为短时状态。
如:官网中的计数器Demo、比如一个PageView组件记录当前的页面应用状态App state
某些状态需要被组件共享访问,当这个状态发生变化的时候,其他组件也需要随之发生联动的变化,这就是应用状态。
举个例子来说明,比如一个电商App,在商品的详情页面,我们把某个商品加入了购物车,那么商品是否放入购物车这个状态,就需要被购物车页面组件所访问,那么这个状态就是应用状态。
试想一下,如果再不使用第三方状态管理框架的情况下,我们可以怎么实现呢,可以使用InheritedWidget定向的传递,可以通过Notification进行通知,可以使用event_bus来进行事件订阅等等,其实我们所说的状态管理框架,也是基于上面说的等几种方式来实现的。
总结来说,要区分短时状态还是应用状态,就看这个状态需不需要被多个组件进行访问,当这个状态一发生变化,其他组件需要随之发生联动变化,就是应用状态,反之,其他组件不需要变化、不受影响,就是短时状态。
Flutter中的有状态组件和无状态组件
在Flutter中,组件根据状态分为,有状态组件StatefulWidget和无状态组件StatelessWidget。
StatelessWidget:无状态的Widget,它无法通过setState设置组件状态进行重绘,它内的属性应该被声明为final,防止改变。
StatefulWidget:有状态的Widget,创建一个StatefulWidget组件时,它同时创建一个State对象,通过与State关联可以达到刷新UI的目的。
State:在Flutter中,Widget和State具有不同的生命周期,Widget是临时对象,用于构建当前状态下的应用程序,而State对象在多次调用build()之间保持不变,允许它们保存信息(状态)。
State生命周期:
Flutter中有哪些可以做到状态管理
State
常用而且使用频繁的一个状态管理类,它必须结合StatefulWidget一起使用,StreamBuilder继承自StatefulWidget,同样是通过setState来管理状态
State缺点:
无法做到跨组件共享数据(这个跨是无关联的,如果是直接的父子关系,我们不认为是跨组件) setState是State的函数,一般我们会将State的子类设置为私有,所以无法做到让别的组件调用State的setState函数来刷新。 setState会成为维护的难点,因为啥哪哪都是。随着页面状态的增多,你可能在调用setState的地方会越来越多,不能统一管理。 处理数据逻辑和视图混合在一起,违反代码设计原则 比如数据库的数据取出来setState到Ui上,这样编写代码,导致状态和UI耦合在一起,不利于测试,不利于复用。 setState是整个Widget重新构建(而且子Widget也会跟着销毁重建),如果页面足够复杂,就会导致严重的性能损耗。建议使用StreamBuilder,原理上也是State,但它做到了子Widget的局部刷新,不会导致整个页面的重建。
InheritedWidget
它的天生特性就是能绑定InheritedWidget与依赖它的子孙组件的依赖关系,并且当InheritedWidget数据发生变化时,可以自动更新依赖的子孙组件!
利用这个特性,我们可以将需要跨组件共享的状态保存在InheritedWidget中,然后在子组件中引用InheritedWidget即可。
专门负责Widget树中数据共享的功能型Widget,如Provider、scoped_model就是基于它开发的。
InheritedWidget缺点:
每次更新都会通知所有的子Widget,无法定向通知/指向性通知,容易造成不必要的刷新。
不支持跨页面(route)的状态,意思是跨树,如果不在一个树中,我们无法获取。
数据是不可变的,必须结合StatefulWidget、ChangeNotifier或者Steam使用。
Notification
它是Flutter中跨层数据共享的一种机制,注意,它不是widget,它提供了dispatch方法,沿着context对应的Element节点向上逐层发送通知
Notification缺点:
不支持跨页面(route)的状态,准确说不支持NotificationListener同级或者父级Widget的状态通知。
本身不支持刷新UI,需要结合State使用。
如果结合State,会导致整个UI的重绘,效率底下不科学。
Stream
纯Dart的实现,跟Flutter没什么关系,扯上关系的就是用StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、BloC、flutter_redux、fish_redux全都用到了Stream的api。
Stream 缺点:
api生涩,不好理解。
需要定制化,才能满足更复杂的场景。
缺点恰恰是它的优点,保证了足够灵活,你更可基于它做一个好的设计,满足当下业务的设计。
为什么要使用状态管理
对于不需要传递的状态或者不需要共享的状态,我们不需要进行复杂的状态管理,单纯依靠setState也可以很好的完成我们的需求。
但是随着产品迭代节奏速度的加快,项目逐渐变得越来越庞大,不同组件之间的数据依赖性越来越高,我们就需要更清晰、明确的处理各个组件之间的数据关系,这时候如果还单单使用setState做状态处理,我们就很难明确的处理数据的流向,终可能会导致数据传递和嵌套逻辑过于复杂,不便于维护和管理,在出现问题的时候,也会花费大量的时间成本来捋清数据之间的关系。
总的来说,对于跨组件(跨页面)之间进行数据共享和传递,而且需要保持状态的一致性和可维护性,这就需要我们对状态进行管理。
常见的状态管理框架有哪些
Provider
Provider是官方文档的例子用的方法. Google 比较推荐的用法. 和BLoC的流式思想相比, Provider是一个观察者模式, 状态改变时要notifyListeners().
Provider的实现在内部还是利用了InheritedWidget,允许将有效信息传递到组件树下的小组件. Provider的好处: dispose指定后会自动被调用, 支持MultiProvider.
Provider从名字上就很容易理解,它就是用于提供数据,无论是在单个页面还是在整个app 都有它自己的解决方案,可以很方便的管理状态。
常用概念:
ChangeNotifier:系统提供的被观察者,数据model需要继承
Provider:订阅者,只用于数据共享管理,提供给子孙节点使用,UpdateShouldNotify Function,用于控制刷新时机
ChangeNotifierProvider:订阅者,不仅能够提供数据供子孙节点使用,还可以在数据改变的时候通知所有消费者。Model变化后会自动通知ChangeNotifierProvider(订阅者),ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子孙Widget就会更新.
MultiProvider:多个订阅者:实际上就是通过每一个provider都实现了的 cloneWithChild方法把自己一层一层包裹起来。
Consumer:消费者,能够在复杂项目中,极大地缩小你的控件刷新范围。多支持6中model
Selector: 消费者,强化的Consumer,支持过滤刷新
使用流程:
添加依赖
创建数据 Model
创建顶层共享数据
顶层Provider包裹
在子页面中获取状态
Provder种类:
Provider:只能提供恒定的数据,不能通知依赖它的子部件刷新。
ListenableProvider: 提供的对象是继承了 Listenable 抽象类的子类,必须实现其 addListener / removeListener 方法,通常不需要。
ChangeNotifierProvider: 对子节点提供一个继承/混入/实现了ChangeNotifier的类,只需要在Model中with ChangeNotifier ,然后在需要刷新状态时调用 notifyListeners 即可。
ValueListenableProvider: 提供实现了继承/混入/实现了ValueListenable的Model,实际上是专门用于处理只有一个单一变化数据的ChangeNotifier。
StreamProvider: 专门用作提供(provide)一条 Single Stream。
FutureProvider:提供了一个 Future 给其子孙节点,并在 Future 完成时,通知依赖的子孙节点进行刷新。
总结:
本质上:Prvioder通过inheritedElement实现局部刷新,通过控制自己实现的Element层来更新UI,通过Element提供的unmount函数回调dispose,实现选择性释放,
其核心类:InheritedProvider
Provider不仅做到了提供数据,而且它拥有着一套完整的解决方案,覆盖了你会遇到的绝大多数情况。就连BLoC未解决的那个棘手的dispose问题,和ScopedModel的侵入性问题,它也都解决了。它能够让你开发出简单、高性能、层次清 的应用。
不足之处:Flutter Widget 构建模式很容易在UI层面上组件化,但是仅仅使用Provider,Model和 View之间还是容易产生依赖。只有通过手动将Model转化为ViewModel这样才能消除掉依赖关系。
Redux
Redux是一种单向数据流架构,可以轻松开发,维护和测试应用程序,也是google推荐的状态管理方式。
原理
所有的状态都存储在Store里。这个Store会放在根Widget.
View拿到Store的状态数据会映射成视图渲染.
Redux不直接让view操作数据,通过dispatch一个action通知Reducer,状态变更
Reducer接收到这个action,根据action状态,生成新的状态,并替换在Store的旧状态.
Store存储了新的状态后,就通知所有使用到了这个状态的View更新(类似setState)。这样我们就能够同步不同view中的状态了.
Redux相关概念
State:数据model
Store 仓库:整个APP的顶层,存储和管理state
Action 动作:通过发起一个Action来告诉Reducer该更新状态了
Reducer 还原:根据Action产生新的状态
StoreProvider: 一个InheritedWidget,内部存储了一个Store。(数据中心)顶层必须是 StoreProvider 开始
StoreConnector: 连接器:需要两个泛型
1)一个是我们创建的 State(ReduxState)
2)一个是 ViewModel,ViewModel决定了converter(转换函数)那边的返回值类型
同时提供了一个StoreStreamListener,本质上是一个StreamBuilderStoreConverter:转换器:类似于Selector中的selector,转换成本Widget想要的数据
StoreStreamListener: 通过监听自己的Stream来完成视图的重建。
StoreBuilder:功能同StoreConnector,StoreConnector主要是有个数据转化的作用,可以对数据先做一些转化操作再赋值到组件上,StoreBuilder是直接将数据给显示在组件上
middleware 中间件:类似拦截器,作用域位于reducer更新状态之前,本质上也是一个函数。
比如当前是添加用户动作,但是我想在添加用户这操作的前面再做一步其他的动作(异步 action ,action 过滤,日志输出,异常报告等),这时候就可以使用中间件middleware,实现MiddlewareClass该类就行。中间件的call方法中有个关键方法next(),大多数情况需要调用,否则中间件的链条断了,后面的中间件和Reducer就不执行了。
Dispatcher:如何通知状态更新呢?通过store.dispatch
Redux页面刷新流程
Redux使用流程:
添加依赖
创建State
创建action
创建reducer
创建store
将Store放入顶层
在子页面中获取Store中的state
发出action
优点:
自动订阅
自动通知
可以定向通知
视图和业务逻辑分离
Redux 的缺点:
Redux 核心仅仅关心数据管理,不关心具体什么场景来使用它,这是它的优点同时也是它的缺点.
在我们实际使用 Redux 中面临两个具体问题.
Redux 的集中和 Component 的分治之间的矛盾.
Redux 的 Reducer 需要一层层手动组装,带来的繁琐性和易错性.
GetX
GetX是Flutter上的一个轻量且强大的解决方案,包括但不限于:
高效的状态管理。
便捷的路由管理。
丰富的Api。
GetX的三项基本原则:
性能:GetX专注于性能和小资源消耗,GetX打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。
效率:GetX的语法非常便捷,并保持了极高的性能,能极大缩短你的开发时长。
结构:GetX可以将界面、逻辑、依赖和路由完全解藕,用起来更清爽,逻辑更清晰,代码更容易维护。
GetX高效的状态管理:
之所以说GetX是高效的状态管理,是因为他不需要堆叠大量的控制、管理代码(如Action、middleware、reducer、state),而且不具有侵入性,可以降低业务和视图间的耦合度。
在使用上,使用GetX的响应式状态管理就像使用setState一样简单(其实本质就是setState),并且GetX可以做到局部刷新。使用GetX实现一个简单的登陆功能
Demo逻辑:HomePage为主页面,事件跳转到登录页面,登录页面登录成功后关闭页面,主页面刷新Text文案内容。
class HomePage extends StatelessWidget {
//实例化的Controller
final LoginController logic = Get.put(LoginController(), tag: 'login');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('LoginPage'),
),
body: Center(
child: GestureDetector(
onTap: () {
Get.to(LoginPage());
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Obx(
> Text(logic.loginStatus.value), =
),
),
),
),
),
);
}
}
登录页面
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//通过find方法,可以找到你已经实例化的Controller
final LoginController loginController = Get.find(tag: 'login');
return Scaffold(
appBar: AppBar(
title: Text('LoginPage'),
),
body: Center(
child: GestureDetector(
onTap: () {
loginController.login();
},
child: Container(
width: 200,
height: 200,
color: Colors.green,
child: Center(child: Text('点我登录')),
),
),
),
);
}
}
controller控制器
class LoginController extends GetxController{
///通过.obs将loginStatus标记为被观察者
///loginStatus是RxString类型的,不是String类型
var loginStatus = '未登录'.obs;
login() => {
loginStatus.value = '已登录',
Get.back(),
// update()
};
}
通过Demo感受到GetX的优点
业务、视图解藕,业务逻辑可以放在Controller中进行处理。
代码简洁,无需创建大量的控制类。
局部刷新,当被观察的数据发生变化时,只有观察者部分会进行刷新,不会整个页面进行刷新。
相同的方法(如login),如果被观察的数据没有发生变化,则不会进行局部刷新。
从此告别StatefulWidget。
更简单的实现跨页面交互事件。
状态管理总结&思考
7.1 如何选择框架
没有哪一种框架可以适配所有的情况,也没有一种框架可以永远适用.
应该根据业务分析适合哪一种,当业务变化时,代码也需要跟着进化,以适配业务的发展.从一开始就介入fish_redux这样的框架,成本高,难度大,只是为了实现一些简单的二级,三级页面,并不是一个好的选择。
7.2 选型原则
侵入性
扩展性
高性能
安全性
驾驭性
易用性
范围性
所有的框架都有侵入性,你同意吗?
目前侵入性比较高的代表ScopedModel,如果你选择的框架只能使用它提供的几个入口,可以放弃使用它。
高性能:也是很重要的,这个需要明白它的原理,看它到底如何做的管理。
安全性:也很重要,看他数据管理通道是否安全稳定。
驾驭性:你说你都不理解你就敢用,出了问题找谁?如果驾驭不了也不要用。
易用性:大家应该都明白,如果用它一个框架需要N多配置,N多实现,放弃吧,不合适。简单才是硬道理。
范围性 :这个特点是flutter中比较明显的,框架选型一定要考虑框架的适用范围,到底是适合做局部管理,还是适合全局管理,要做一个实际的考量。
7.3 多种状态管理框架是否可以同时使用?
当然可以,你用了redux,就不允许setstate()了? 显然不是.如何同时使用不同的框架能满足你的需求,使你的性能更好,使用更方便,可读性更强那就使用吧。