服务器 频道

Flutter|一文搞懂何谓状态管理

  01 什么是状态管理

  随着“大前端”概念流行的同时,响应式编程的理念也随之被越来越多的人所了解和学习,要了解和学习响应式的编程框架就一定离不开状态管理,客户端Android、iOS是通过明确的命令式指令去控制我们的UI变化,如setText,而在响应式编程下,我们只需要描述好UI和状态之间的关系,然后专注于状态的改变就好了,框架会根据状态的变化来自动更新UI。

  总结来说就是:状态管理就是当某个状态发生改变的时候,告知使用该状态的状态监听者,让状态所监听的属性随知改变,从而达到联动效果。

  02 不同的状态管理分类

  短时状态Ephemeral state某些状态、或是可以理解为某些数据只需要在当前的Widget中访问和使用,不需要对这些状态进行共享访问,你需要的只是一个StatefulWidget组件,依靠这个StatefulWidget组件自己的State类自己管理即可,不需要使用状态管理框架去管理这种状态,这些状态可以称之为短时状态。如:官网中的计数器Demo、比如一个PageView组件记录当前的页面

  应用状态App state某些状态需要被组件共享访问,当这个状态发生变化的时候,其他组件也需要随之发生联动的变化,这就是应用状态。举个例子来说明,比如一个电商App,在商品的详情页面,我们把某个商品加入了购物车,那么商品是否放入购物车这个状态,就需要被购物车页面组件所访问,那么这个状态就是应用状态。试想一下,如果再不使用第三方状态管理框架的情况下,我们可以怎么实现呢,可以使用InheritedWidget定向的传递,可以通过Notification进行通知,可以使用event_bus来进行事件订阅等等,其实我们所说的状态管理框架,也是基于上面说的等几种方式来实现的。

总结来说,要区分短时状态还是应用状态,就看这个状态需不需要被多个组件进行访问,当这个状态一发生变化,其他组件需要随之发生联动变化,就是应用状态,反之,其他组件不需要变化、不受影响,就是短时状态。

  03 Flutter中的有状态组件和无状态组件

  在Flutter中,组件根据状态分为,有状态组件StatefulWidget和无状态组件StatelessWidget。

  StatelessWidget:无状态的Widget,它无法通过setState设置组件状态进行重绘,它内的属性应该被声明为final,防止改变。

  StatefulWidget:有状态的Widget,创建一个StatefulWidget组件时,它同时创建一个State对象,通过与State关联可以达到刷新UI的目的。

  State:在Flutter中,Widget和State具有不同的生命周期,Widget是临时对象,用于构建当前状态下的应用程序,而State对象在多次调用build()之间保持不变,允许它们保存信息(状态)。

  State生命周期:

  

  04 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生涩,不好理解。

  需要定制化,才能满足更复杂的场景。

  缺点恰恰是它的优点,保证了足够灵活,你更可基于它做一个好的设计,满足当下业务的设计。

  05 为什么要使用状态管理

  对于不需要传递的状态或者不需要共享的状态,我们不需要进行复杂的状态管理,单纯依靠setState也可以很好的完成我们的需求。

  但是随着产品迭代节奏速度的加快,项目逐渐变得越来越庞大,不同组件之间的数据依赖性越来越高,我们就需要更清晰、明确的处理各个组件之间的数据关系,这时候如果还单单使用setState做状态处理,我们就很难明确的处理数据的流向,最终可能会导致数据传递和嵌套逻辑过于复杂,不便于维护和管理,在出现问题的时候,也会花费大量的时间成本来捋清数据之间的关系。

  总的来说,对于跨组件(跨页面)之间进行数据共享和传递,而且需要保持状态的一致性和可维护性,这就需要我们对状态进行管理。

  06 常见的状态管理框架有哪些

  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,本质上是一个StreamBuilder

  StoreConverter:转换器:类似于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文案内容。

  登录页面

  controller控制器

  通过Demo感受到GetX的优点

  业务、视图解藕,业务逻辑可以放在Controller中进行处理。

  代码简洁,无需创建大量的控制类。

  局部刷新,当被观察的数据发生变化时,只有观察者部分会进行刷新,不会整个页面进行刷新。

  相同的方法(如login),如果被观察的数据没有发生变化,则不会进行局部刷新。

  从此告别StatefulWidget。

  更简单的实现跨页面交互事件。

  07 状态管理总结&思考

  7.1 如何选择框架

  没有哪一种框架可以适配所有的情况,也没有一种框架可以永远适用.应该根据业务分析适合哪一种,当业务变化时,代码也需要跟着进化,以适配业务的发展.从一开始就介入fish_redux这样的框架,成本高,难度大,只是为了实现一些简单的二级,三级页面,并不是一个好的选择。

  7.2 选型原则

  侵入性

  扩展性

  高性能

  安全性

  驾驭性

  易用性

  范围性

  所有的框架都有侵入性,你同意吗?

  目前侵入性比较高的代表ScopedModel,如果你选择的框架只能使用它提供的几个入口,可以放弃使用它。

  高性能:也是很重要的,这个需要明白它的原理,看它到底如何做的管理。安全性:也很重要,看他数据管理通道是否安全稳定。驾驭性:你说你都不理解你就敢用,出了问题找谁?如果驾驭不了也不要用。易用性:大家应该都明白,如果用它一个框架需要N多配置,N多实现,放弃吧,不合适。简单才是硬道理。范围性 :这个特点是flutter中比较明显的,框架选型一定要考虑框架的适用范围,到底是适合做局部管理,还是适合全局管理,要做一个实际的考量。

  7.3 多种状态管理框架是否可以同时使用?

  当然可以,你用了redux,就不允许setstate()了? 显然不是.如何同时使用不同的框架能满足你的需求,使你的性能更好,使用更方便,可读性更强那就使用吧。

0
相关文章