2019 Google I/O 大会,google就推出Provider,成为官方推荐的状态管理方式之一,Flutter 状态管理一直是个很热门的话题,并且状态管理的库也是超级多,这确实是咱们每个作Flutter开发难以免的一道坎,既然这么重要,咱们如何去理解它,如何使用它,如何作到更好呢?接下来让我告诉你答案html
该文章已经经历了一周的迭代,预计还要一周左右,要作一个全面的分析,固然要每一个细节都要关注到,若是您以为好,请不要吝啬您的大拇指,顺便点个赞哦,么么哒,若是有不对的地方提出来,一个地方一个红包奖励哦,爱大家。react
一张图告诉你,我要讲的主要内容。下面将围绕这八个方面来说。七个理论,一个实践。android
咱们知道最基本的程序是什么:git
数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿了整个程序世界,首先两者表现为不可分割的关系。其实Flutter不就是一个程序吗,那咱们面临的最底层的问题仍是算法和数据结构,因此咱们推导出github
那状态管理是什么?我也用公式来表达一下,以下:算法
瞬间秒懂有没有?来看一个代码例子:数据库
class ThemeBloc { final _themeStreamController = StreamController<AppTheme>(); get changeTheTheme => _themeStreamController.sink.add; get darkThemeIsEnabled => _themeStreamController.stream; dispose() { _themeStreamController.close(); } } final bloc = ThemeBloc(); class AppTheme { ThemeData themeData; AppTheme(this.themeData); } /// 绑定到UI StreamBuilder<AppTheme>( initialData: AppTheme.LIGHT_THEME, stream: bloc.darkThemeIsEnabled, builder: (context, AsyncSnapshot<AppTheme> snapshot) { return MaterialApp( title: 'Jetpack', theme: snapshot.data.themeData, home: PageHome(), routes: <String, WidgetBuilder>{ "/pageChatGroup": (context) => PageChatGroup(), "/LaoMeng": (context) => LaoMeng(), }, ); })
这样一整套代码的逻辑就是咱们所说的Flutter状态管理,这样解释你们理解了吗?再细说,算法就是咱们如何管理,数据结构就是数据状态,状态管理的本质仍是如何经过合理的算法管理数据,如何取,如何接收等,最终展现在UI上,经过UI的变动来体现状态的管理逻辑。redux
这里就须要明白一个事情,Flutter的不少优秀的设计都来源于React,对于react来讲,同级组件之间的通讯尤其麻烦,或者是很是麻烦了,因此咱们把全部须要多个组件使用的state拿出来,整合到顶部容器,进行分发。状态管理能够实现组件通讯、跨组件数据储存。推荐阅读对 React 状态管理的理解及方案对比,那么对于Flutter来讲呢?你知道Android、Ios等原生于Flutter最本质的区别吗?来看一段代码:api
//android TextView tv = TextView() tv.setText("text")
///flutter setState{ text = "text" }
从上面代码咱们看出,Android的状态变动是经过具体的组件直接赋值,若是页面所有变动,你是否是须要每个都设置一遍呢?,而Flutter的变动就简单粗暴,setState搞定,它背后的逻辑是从新build整个页面,发现有变动,再将新的数据赋值,其实Android、Ios与flutter的本质的区别就是数据与视图彻底分离,固然Android也出现了UI绑定框架,彷佛跟React、Flutter愈来愈像,因此这也在另外一方面凸显出了,Flutter设计的先进性,没有什么创新,但更符合将来感,回过头来,仔细想想,这样设计有什么弊端?安全
对了你猜对了:页面如何刷新才是Flutter的关键,作Android的同窗确定也面临着一个问题,页面的重绘致使的丢帧问题,为了更好,咱们不少时候都选择了局部刷新来优化对吧,Android、Ios已经很明确的告诉UI要刷新什么更新什么,而对于Flutter来讲,这一点很不清晰,虽然Flutter也作了相似虚拟Dom优化重绘逻辑,但这些远远不够的,如何合理的更新UI才是最主要的,这个时候一大堆的状态管理就出来了,固然状态管理也不是仅仅为了解决更新问题。
我再抛出一个问题,若是我有一个widget A,我想在另一个widget B中改变widget A的一个状态,或者从网络、数据库取到数据,而后刷新它,怎么作?咱们来模拟一下,来看代码
糟糕的状态管理代码
class WidgetTest extends StatefulWidget { @override _WidgetTestState createState() => _WidgetTestState(); } class _WidgetTestState extends State<WidgetTest> { @override Widget build(BuildContext context) { return Container( child: Column( children: <Widget>[ WidgetA(), WidgetB() ], ), ); } } _WidgetAState _widgetAState; class WidgetA extends StatefulWidget { @override _WidgetAState createState() { _widgetAState = _WidgetAState(); return _widgetAState; } } class _WidgetAState extends State<WidgetA> { var title = ""; @override Widget build(BuildContext context) { return Container( child: Text(title), ); } } class WidgetB extends StatefulWidget { @override _WidgetBState createState() => _WidgetBState(); } class _WidgetBState extends State<WidgetB> { @override Widget build(BuildContext context) { return Container( child: RaisedButton( onPressed: () { _widgetAState.setState(() { _widgetAState.title = "WidgetB"; }); }, ), ); } }
WidgetTest页面有两个widget,分别是WidgetA、WidgetB,WidgetB经过RaisedButton的onPressed来改变WidgetA的Text,怎么作到的呢,直接用WidgetA的_WidgetAState对象提供的setState函数来变动,没什么问题对吧,并且功能实现了,但你仔细思考一下,这有什么问题呢?
如何变好呢
这就须要选择一种合适的状态管理方式。
状态管理的目标
其实咱们作状态管理,不只仅是由于它的特色,而为了更好架构,不是吗?
这些不牢牢是状态管理的目的,也是咱们作一款优秀应用的基础架构哦。
举个例子,以下方的_index,这就是一个局部或者短暂状态,只须要StatefulWidget处理便可完成
class MyHomepage extends StatefulWidget { @override _MyHomepageState createState() => _MyHomepageState(); } class _MyHomepageState extends State<MyHomepage> { int _index = 0; @override Widget build(BuildContext context) { return BottomNavigationBar( currentIndex: _index, onTap: (newIndex) { setState(() { _index = newIndex; }); }, // ... items ... ); } }
例如:
状态分类官方定义
没有明确的通用规则来区分特定变量是短暂状态仍是应用程序状态。有时,您必须将一个重构为另外一个。例如,您将从一个明显的短暂状态开始,可是随着您的应用程序功能的增加,可能须要将其移至应用程序状态。 出于这个缘由,请使用下图进行分类:
总之,任何Flutter应用程序中都有两种概念性的状态类型。临时状态可使用State和setState()来实现,而且一般是单个窗口小部件的本地状态。剩下的就是您的应用状态。两种类型在任何Flutter应用程序中都有本身的位置,二者之间的划分取决于您本身的喜爱和应用程序的复杂性
没有最好的管理方式,只有最合适的管理方式
底层逻辑我想告诉你的是,Flutter中目前有哪些能够作到状态管理,有什么缺点,适合作什么不适合作什么,只有你彻底明白底层逻辑,才不会畏惧复杂的逻辑,即便是复杂的逻辑,你也能选择合理的方式去管理状态。
StatefulWidget、StreamBuilder状态管理方式
专门负责Widget树中数据共享的功能型Widget,如Provider、scoped_model就是基于它开发
与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但二者都在本身的Widget树中传递,没法跨越树传递。
数据流 如Bloc、flutter_redux、fish_redux等也都基于它来作实现
为何列这些东西?由于如今大部分流行的状态管理都离不开它们。理解它们比理解那些吹本身牛逼的框架要好的多。请关注底层逻辑,这样你才能游刃有余。下面咱们一个个分析一下:
State 是咱们经常使用并且使用最频繁的一个状态管理类,它必须结合StatefulWidget一块儿使用,StreamBuilder继承自StatefulWidget,一样是经过setState来管理状态
举个例子来看下:
class TapboxA extends StatefulWidget { TapboxA({Key key}) : super(key: key); @override _TapboxAState createState() => _TapboxAState(); } class _TapboxAState extends State<TapboxA> { bool _active = false; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( _active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }
引用官方的例子,这里_active状态就是经过State提供的setState函数来实现的
为何会让State去管理状态,而不是Widget自己呢?Flutter设计时让Widget自己是不变的,相似固定的配置信息,那么就须要一个角色来控制它,State就出现了,但State的任何更改都会强制整个Widget从新构建,固然你也能够覆盖必要方法本身控制逻辑。
再看个例子:
class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatelessWidget { TapboxB({Key key, this.active: false, @required this.onChanged}) : super(key: key); final bool active; final ValueChanged<bool> onChanged; void _handleTap() { onChanged(!active); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }
从这里你看出什么?对了,父组件能够经过setState来刷新子Widget的状态变化,因此得出以下观点
注意
setState是整个Widget从新构建(并且子Widget也会跟着销毁重建),这个点也是为何不推荐你大量使用StatefulWidget的缘由。若是页面足够复杂,就会致使严重的性能损耗。如何优化呢?建议使用StreamBuilder,它原理上也是State,但它作到了子Widget的局部刷新,不会致使整个页面的重建,是否是就好不少了呢?
从上面的代码咱们分析一下它的缺点
setState是State的函数,通常咱们会将State的子类设置为私有,因此没法作到让别的组件调用State的setState函数来刷新
随着页面状态的增多,你可能在调用setState的地方会愈来愈多,不能统一管理
好比数据库的数据取出来setState到Ui上,这样编写代码,致使状态和UI耦合在一块儿,不利于测试,不利于复用。
固然反过来说,不是由于它有缺点咱们就不使用了,咱们追求的简单高效,简单实现,高效运行,当复杂到须要更好的管理的时候再重构。一个基本原则就是,状态是否须要跨组件使用,若是须要那就用别的办法管理状态而不是State管理。
InheritedWidget是一个无私的Widget,它能够把本身的状态数据,无私的交给全部的子Widget,全部的子Widget能够无条件的继承它的状态。就这么一个东西。有了State咱们为何还须要它呢?咱们已经知道,State是能够更新直接子Widget的状态,但若是是子Widget的子Widget呢,因此说InheritedWidget的存在,一是为了更简单的获取状态,二是你们都共享这个状态,举个例子
class InheritedWidgetDemo extends InheritedWidget { final int accountId; InheritedWidgetDemo(this.accountId, {Key key, Widget child}) : super(key: key, child: child); @override bool updateShouldNotify(InheritedWidgetDemo old) => accountId != old.accountId; static InheritedWidgetDemo of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<InheritedWidgetDemo>(); } } class MyPage extends StatelessWidget { final int accountId; MyPage(this.accountId); Widget build(BuildContext context) { return new InheritedWidgetDemo( accountId, child: const MyWidget(), ); } } class MyWidget extends StatelessWidget { const MyWidget(); Widget build(BuildContext context) { return MyOtherWidget(); } } class MyOtherWidget extends StatelessWidget { @override Widget build(BuildContext context) { final myInheritedWidget = InheritedWidgetDemo.of(context); print(myInheritedWidget.accountId); } }
有的人想了,InheritedWidget这么好用,那我把整个App的状态都存进来怎么样?相似这样
class AppContext { int teamId; String teamName; int studentId; String studentName; int classId; ... }
其实这样很差,咱们不光是要作技术上的组件化,更要关注的是业务,对业务的充分理解并实现模块化分工,在使用InheritedWidget时候特别是要注意这一点,更推荐你使用该方案:
class TeamContext { int teamId; String teamName; } class StudentContext { int studentId; String studentName; } class ClassContext { int classId; ... }
注意
它的数据是只读的,虽然很无私,但子widget不能修改,那么如何修改呢?
举个例子:
class Item { String reference; Item(this.reference); } class _MyInherited extends InheritedWidget { _MyInherited({ Key key, @required Widget child, @required this.data, }) : super(key: key, child: child); final MyInheritedWidgetState data; @override bool updateShouldNotify(_MyInherited oldWidget) { return true; } } class MyInheritedWidget extends StatefulWidget { MyInheritedWidget({ Key key, this.child, }): super(key: key); final Widget child; @override MyInheritedWidgetState createState() => new MyInheritedWidgetState(); static MyInheritedWidgetState of(BuildContext context){ return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data; } } class MyInheritedWidgetState extends State<MyInheritedWidget>{ /// List of Items List<Item> _items = <Item>[]; /// Getter (number of items) int get itemsCount => _items.length; /// Helper method to add an Item void addItem(String reference){ setState((){ _items.add(new Item(reference)); }); } @override Widget build(BuildContext context){ return new _MyInherited( data: this, child: widget.child, ); } } class MyTree extends StatefulWidget { @override _MyTreeState createState() => new _MyTreeState(); } class _MyTreeState extends State<MyTree> { @override Widget build(BuildContext context) { return new MyInheritedWidget( child: new Scaffold( appBar: new AppBar( title: new Text('Title'), ), body: new Column( children: <Widget>[ new WidgetA(), new Container( child: new Row( children: <Widget>[ new Icon(Icons.shopping_cart), new WidgetB(), new WidgetC(), ], ), ), ], ), ), ); } } class WidgetA extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context); return new Container( child: new RaisedButton( child: new Text('Add Item'), onPressed: () { state.addItem('new item'); }, ), ); } } class WidgetB extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context); return new Text('${state.itemsCount}'); } } class WidgetC extends StatelessWidget { @override Widget build(BuildContext context) { return new Text('I am Widget C'); } }
该例子引用自widget-state-context-inheritedwidget/欢迎阅读学习哦
看了一下日志输出如图:
有没有发现一个问题?当MyInheritedWidgetState.addItem,致使setState被调用,而后就触发了WidgetA、WidgetB的build的方法,而WidgetA根本不须要从新build,这不是浪费吗?那么咱们如何优化呢?
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){ return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data; }
经过抽象rebuild属性来控制是否须要从新build
final MyInheritedWidgetState state = MyInheritedWidget.of(context,false);
而后用的时候加以参数控制,改完代码,再看下日志:
看,已经生效了。你如今是否是对InheritedWidget有了更清晰的认识了呢?但说到它就不得不提InheritedModel,它是InheritedWidget的子类,InheritedModel能够作到部分数据改变的时候才会重建,你能够修改上面例子
class _MyInheritedWidget extends InheritedModel { static MyInheritedWidgetState of(BuildContext context, String aspect) { return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data; } @override bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) { return aspects.contains('true'); } }
调用修改成:
///不容许从新build final MyInheritedWidgetState state = MyInheritedWidget.of(context,"false"); ///容许从新build final MyInheritedWidgetState state = MyInheritedWidget.of(context,"true");
推荐阅读
inheritedmodel-vs-inheritedwidget
https://juju.one/inheritedwidget-inheritedmodel/
widget-state-context-inheritedwidget/
经过上面的分析,咱们来看下它的缺点
通过一系列的举例和验证,你也基本的掌握了InheritedWidget了吧,这个组件特别适合在同一树型Widget中,抽象出公有状态,每个子Widget或者孙Widget均可以获取该状态,咱们还能够经过手段控制rebuild的粒度来优化重绘逻辑,但它更适合从上往下传递,若是是从下往上传递,咱们如何作到呢?请往下看,立刻给你解答
它是Flutter中跨层数据共享的一种机制,注意,它不是widget,它提供了dispatch方法,来让咱们沿着context对应的Element节点向上逐层发送通知
具个简单例子看下
class TestNotification extends Notification { final int test; TestNotification(this.test); } var a = 0; // ignore: must_be_immutable class WidgetNotification extends StatelessWidget { final String btnText; WidgetNotification({Key key, this.btnText}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: RaisedButton( child: Text(btnText), onPressed: () { var b = ++a; debugPrint(b.toString()); TestNotification(b).dispatch(context); }, ), ); } } class WidgetListener extends StatefulWidget { @override _WidgetListenerState createState() => _WidgetListenerState(); } class _WidgetListenerState extends State<WidgetListener> { int _test = 1; @override Widget build(BuildContext context) { return Container( child: Column( children: <Widget>[ NotificationListener<TestNotification>( child: Column( children: <Widget>[ Text("监听$_test"), WidgetNotification(btnText: "子Widget",) ], ), onNotification: (TestNotification notification) { setState(() { _test = notification.test; }); return true; }, ), WidgetNotification(btnText: "非子Widget",) ], ), ); } }
因此说在使用Notification的时候要注意,若是遇到没法收到通知的状况,考虑是不是Notification 未在NotificationListener的内部发出通知,这个必定要注意。
一样的思路,我想看下Notification是如何刷新Ui的
在代码里加入了跟通知可有可无的WidgetC
这么看来,你觉得是Notification致使的吗?我把这个注释掉,如图
再运行看下,连续点击了八次
原来是State的缘由,那么这种状况咱们如何优化呢?这就用到了Stream了,请接着往下继续看哦。
推荐阅读
flutter-notifications-bubble-up-and-values-go-down
使用起来很简单,但在刷新UI方面须要注意,若是页面复杂度很高,致使可有可无的组件跟着刷新,得不偿失,还须要另找蹊径,躲开这些坑,下面我来介绍如何完美躲闪,重磅来袭Stream。
它实际上是纯Dart的实现,跟Flutter没什么关系,扯上关系的就是用StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、BloC、flutter_redux全都用到了Stream的api。因此学习它才是咱们掌握状态管理的一个关键
推荐阅读
我本身写的StreamBuilder源码分析
大神写的Stream全面分析
缺点偏偏是它的优势,保证了足够灵活,你更可基于它作一个好的设计,知足当下业务的设计。
经过对State、InheritedWidget、Notification、Stream的学习,你是否是以为,Flutter的状态管理也就这些了呢?不必定哈,我也在不断的学习,若是碰到新的技术,继续分享给大家哦。难道这就完了吗?没有,其实咱们只是学了第一步,是什么,如何用,尚未讨论怎么用好呢?须要什么标准吗,固然有,下面经过个人项目实战经验来提出一个基本原则,超过这个原则你就是在破坏平衡,请往下看。
这个原则来源于,Flutter的性能优化,局部刷新确定比全局刷新要好不少,那么咱们在管理状态的同时,也要考虑该状态究竟是局部仍是全局,从而编写正确的逻辑。
用“_”私有化状态,由于当开发人员众多,当别人看到你的变量的时候,第一反应可能不是找你提供的方法,而是直接对变量操做,那就有可能出现想不到的后果,若是他只能调用你提供的方法,那他就要遵循你方法的逻辑,避免数据被处理错误。
不少时候页面的重建都会调用build函数,也就是说,在一个生命周期内,build函数是屡次被调用的,因此你就要考虑数据的初始化或者刷新怎么样才能合理。
经过了解它们的弊端来规避一些风险,综合考虑,选框架不易,且行且珍惜。
全部的框架都有侵入性,你赞成吗?不一样意请左转,前面有个坑,你能够跳过去。目前侵入性比较高的表明ScopedModel,为啥?由于它是用extend实现的,须要继承实现的基本不是什么好实现,你赞成吗?同上。
扩展性就不用说了,若是你选择的框架只能使用它提供的几个入口,那么请你放弃使用它。高性能也是很重要的,这个须要明白它的原理,看它到底如何作的管理。安全性也很重要,看他数据管理通道是否安全稳定。驾驭性,你说你都不理解你就敢用,出了问题找谁?若是驾驭不了也不要用。易用性你们应该都明白,若是用它一个框架须要N多配置,N多实现,放弃吧,不合适。简单才是硬道理。
范围性
这个特色是flutter中比较明显的,框架选型必定要考虑框架的适用范围,究竟是适合作局部管理,仍是适合全局管理,要作一个实际的考量。
若是是初期,建议多使用Stream、State、Notification来自行处理,顺便学习源码,多理解,多实践。有架构能力的就能够着手封装了,提供更简单的使用方式
若是是后期,固然也是在前面的基础之上,再去考虑使用Provider、redux等复杂的框架,原则上要吃透源码,不然不建议使用。
你觉得使用框架就能万事大吉了?性能优化是一个不变的话题,包括Provider在内的,若是你使用不当,照样出现页面的性能损耗严重,因此你又回到了为啥会这样,请你学习上面的底层逻辑,谢谢🙏
经过这期分享,你是否是对Flutter的状态管理有了一个从新的认识呢?若是对你有帮住,请点一下下面的赞哦。谢谢🙏。