看了一下日志输出如图:
有没有发现一个问题?当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
widget-state-context-inheritedwidget/
InheritedWidget 缺点
通过上面的分析,我们来看下它的缺点
- 容易造成不必要的刷新
- 不支持跨页面(route)的状态,意思是跨树,如果不在一个树中,我们无法获取
- 数据是不可变的,必须结合StatefulWidget、ChangeNotifier或者Steam使用
InheritedWidget 小结
经过一系列的举例和验证,你也基本的掌握了InheritedWidget了吧,这个组件特别适合在同一树型Widget中,抽象出公有状态,每一个子Widget或者孙Widget都可以获取该状态,我们还可以通过手段控制rebuild的粒度来优化重绘逻辑,但它更适合从上往下传递,如果是从下往上传递,我们如何做到呢?请往下看,马上给你解答
Notification
它是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",) ], ), ); } }
- 定义TestNotification通知的实现
- WidgetNotification 负责通知结果,通过RaisedButton的点击事件,将数据a传递出去,通过Notification提供的dispatch方法向上传递
- WidgetListener通过Widget NotificationListener来监听数据变化,最终通过setState变更数据
- WidgetNotification 实例化了两次,一次在NotificationListener的树内部,一个在NotificationListener的外部,经过测试发现,在外部的WidgetNotification并不能通知到内容变化。
所以说在使用Notification的时候要注意,如果遇到无法收到通知的情况,考虑是否是Notification 未在NotificationListener的内部发出通知,这个一定要注意。
同样的思路,我想看下Notification是如何刷新Ui的 在代码里加入了跟通知无关紧要的WidgetC
这么看来,你以为是Notification导致的吗?我把这个注释掉,如图
再运行看下,连续点击了八次
原来是State的原因,那么这种情况我们如何优化呢?这就用到了Stream了,请接着往下继续看哦。
推荐阅读
flutter-notifications-bubble-up-and-values-go-down
Notification缺点
- 不支持跨页面(route)的状态,准备的说不支持NotificationListener同级或者父级Widget的状态通知
- 本身不支持刷新UI,需要结合State使用
- 如果结合State,会导致整个UI的重绘,效率底下不科学
Notification小结
使用起来很简单,但在刷新UI方面需要注意,如果页面复杂度很高,导致无关紧要的组件跟着刷新,得不偿失,还需要另找蹊径,躲开这些坑,下面我来介绍如何完美躲闪,重磅来袭Stream。
Stream
它其实是纯Dart的实现,跟Flutter没什么关系,扯上关系的就是用StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、BloC、flutter_redux全都用到了Stream的api。所以学习它才是我们掌握状态管理的一个关键
推荐阅读我自己写的StreamBuilder源码分析大神写的Stream全面分析
Stream 缺点
- api生涩,不好理解
- 需要定制化,才能满足更复杂的场景
缺点恰恰是它的优点,保证了足够灵活,你更可基于它做一个好的设计,满足当下业务的设计。
小结
通过对State、InheritedWidget、Notification、Stream的学习,你是不是觉得,Flutter的状态管理也就这些了呢?不一定哈,我也在不断的学习,如果碰到新的技术,继续分享给你们哦。难道这就完了吗?没有,其实我们只是学了第一步,是什么,如何用,还没有讨论怎么用好呢?需要什么标准吗,当然有,下面通过我的项目实战经验来提出一个基本原则,超过这个原则你就是在破坏平衡,请往下看。
状态管理的使用原则
局部管理优于全局
这个原则来源于,Flutter的性能优化,局部刷新肯定比全局刷新要好很多,那么我们在管理状态的同时,也要考虑该状态到底是局部还是全局,从而编写正确的逻辑。
保持数据安全性
用“_”私有化状态,因为当开发人员众多,当别人看到你的变量的时候,第一反应可能不是找你提供的方法,而是直接对变量操作,那就有可能出现想不到的后果,如果他只能调用你提供的方法,那他就要遵循你方法的逻辑,避免数据被处理错误。
考虑页面重新build带来的影响
很多时候页面的重建都会调用build函数,也就是说,在一个生命周期内,build函数是多次被调用的,所以你就要考虑数据的初始化或者刷新怎么样才能合理。
使用成熟状态管理库弊端
- 增加代码复杂性
- 框架bug修复需要时间等待
- 不理解框架原理导致使用方式不对,反而带来更多问题
- 选型错误导致不符合应用要求
- 与团队风格冲突不适用 通过了解它们的弊端来规避一些风险,综合考虑,选框架不易,且行且珍惜。
选型原则
- 侵入性
- 扩展性
- 高性能
- 安全性
- 驾驭性
- 易用性
- 范围性
所有的框架都有侵入性,你同意吗?不同意请左转,前面有个坑,你可以跳过去。目前侵入性比较高的代表ScopedModel,为啥?因为它是用extend实现的,需要继承实现的基本不是什么好实现,你同意吗?同上。 扩展性就不用说了,如果你选择的框架只能使用它提供的几个入口,那么请你放弃使用它。高性能也是很重要的,这个需要明白它的原理,看它到底如何做的管理。安全性也很重要,看他数据管理通道是否安全稳定。驾驭性,你说你都不理解你就敢用,出了问题找谁?如果驾驭不了也不要用。易用性大家应该都明白,如果用它一个框架需要N多配置,N多实现,放弃吧,不合适。简单才是硬道理。
范围性这个特点是flutter中比较明显的,框架选型一定要考虑框架的适用范围,到底是适合做局部管理,还是适合全局管理,要做一个实际的考量。
推荐用法
如果是初期,建议多使用Stream、State、Notification来自行处理,顺便学习源码,多理解,多实践。有架构能力的就可以着手封装了,提供更简单的使用方式 如果是后期,当然也是在前面的基础之上,再去考虑使用Provider、redux等复杂的框架,原则上要吃透源码,否则不建议使用。
注意
你以为使用框架就能万事大吉了?性能优化是一个不变的话题,包括Provider在内的,如果你使用不当,照样出现页面的性能损耗严重,所以你又回到了为啥会这样,请你学习上面的底层逻辑,谢谢🙏
总结
通过这期分享,你是不是对Flutter的状态管理有了一个重新的认识呢?如果对你有帮住,请点一下下面的赞哦。谢谢🙏。