前言
2019 Google I/O 大会,google就推出Provider,成为官方推荐的状态管理方式之一,Flutter 状态管理一直是个很热门的话题,而且状态管理的库也是超级多,这确实是我们每一个做Flutter开发难以避免的一道坎,既然这么重要,我们如何去理解它,如何使用它,如何做到更好呢?接下来让我告诉你答案
主要内容
一张图告诉你,我要讲的主要内容。下面将围绕这八个方面来讲。七个理论,一个实践。
- 状态管理是什么
- 为什么需要状态管理
- 状态管理基本分类
- 状态管理的底层逻辑
- 状态管理的使用原则
- 使用成熟状态管理库的弊端
- 选择状态管理库的原则
- Provider 深入分析(学以致用)
状态管理是什么
我们知道最基本的程序是什么:
- 程序=算法+数据结构
数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿了整个程序世界,首先二者表现为不可分割的关系。其实Flutter不就是一个程序吗,那我们面临的最底层的问题还是算法和数据结构,所以我们推导出 - Flutter=算法+数据结构
那状态管理是什么?我也用公式来表达一下,如下: - Flutter状态管理=算法+数据结构+UI绑定
瞬间秒懂有没有?来看一个代码例子:
class ThemeBloc {
final _themeStreamController = StreamController();
get changeTheTheme => _themeStreamController.sink.add;
get darkThemeIsEnabled => _themeStreamController.stream;
dispose() {
_themeStreamController.close();
}
}
final bloc = ThemeBloc();
class AppTheme {
ThemeData themeData;
AppTheme(this.themeData);
}
/// 绑定到UI
StreamBuilder(
initialData: AppTheme.LIGHT_THEME,
stream: bloc.darkThemeIsEnabled,
builder: (context, AsyncSnapshot snapshot) {
return MaterialApp(
title: 'Jetpack',
theme: snapshot.data.themeData,
home: PageHome(),
routes: {
"/pageChatGroup": (context) => PageChatGroup(),
"/LaoMeng": (context) => LaoMeng(),
},
);
})
- AppTheme 是数据结构
- changeTheTheme 是算法
- StreamBuilder 是绑定UI
这样一整套代码的逻辑就是我们所说的Flutter状态管理,这样解释大家理解了吗?再细说,算法就是我们如何管理,数据结构就是数据状态,状态管理的本质还是如何通过合理的算法管理数据,如何取,如何接收等,最终展示在UI上,通过UI的变更来体现状态的管理逻辑。
为什么需要
这里就需要明白一个事情,Flutter的很多优秀的设计都来源于React,对于react来说,同级组件之间的通信尤为麻烦,或者是非常麻烦了,所以我们把所有需要多个组件使用的state拿出来,整合到顶部容器,进行分发。状态管理可以实现组件通信、跨组件数据储存。推荐阅读对 React 状态管理的理解及方案对比,那么对于Flutter来说呢?你知道Android、Ios等原生于Flutter最本质的区别吗?来看一段代码:
//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才是最主要的,这个时候一大堆的状态管理就出来了,当然状态管理也不是仅仅为了解决更新问题。
架构纬度来看
- 代码要层次分明,易维护
- 也要可扩展,可以动态替换UI而不影响算法逻辑
- 也要可靠,保持数据的稳定伸缩
这些不紧紧是状态管理的目的,也是我们做一款优秀应用的基础架构哦。
基本分类
- 局部管理 如:登录页面的用户名与密码就属于局部管理
- 全局管理 如:登录页面的登录状态,是一个全局状态,其他页面都需要,也可以是局部,不要太纠结这个问题
底层逻辑
- State
StatefulWidget、StreamBuilder状态管理方式 - InheritedWidget
专门负责Widget树中数据共享的功能型Widget,如Provider、scoped_model就是基于它开发 - Notification
与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但两者都在自己的Widget树中传递,无法跨越树传递。 - Stream
数据流 如Bloc、flutter_redux、fish_redux等也都基于它来做实现
为什么列这些东西?因为现在大部分流行的状态管理都离不开它们。理解它们比理解那些吹自己牛逼的框架要好的多。请关注底层逻辑,这样你才能游刃有余。下面我们一个个分析一下:
State
State 是我们常用而且使用最频繁的一个状态管理类,我们经常会用StatefulWidget来管理状态的变化,StreamBuilder继承自StatefulWidget也不用多说它的原理。
为什么会有它呢?请看Flutter渲染步骤。
Widget=>Element=>RenderObject
Widget本身是不变的,那么就需要一个角色来控制它,State就出现了,State的任何更改都会强制整个Widget重新构建
注意
是整个Widget重新构建(而且子Widget也会跟着销毁重建),这个点也是为什么不推荐你大量使用StatefulWidget的原因。如果页面足够复杂,就会导致严重的性能损耗。如何优化呢?建议使用StreamBuilder,它原理上也是State,但它做到了子Widget的局部刷新,不会导致整个页面的重建,是不是就好很多了呢?
State缺点
- 无法做到跨组件共享数据
- setState会成为维护的难点,因为啥哪哪都是。
- 处理数据逻辑和视图混合在一起,违反代码设计原则
InheritedWidget
InheritedWidget是一个无私的Widget,它可以把自己的状态数据,无私的交给所有的子Widget,所有的子Widget可以无条件的继承它的状态。就这么一个东西。
注意
它的数据是只读的,虽然很无私,但子widget不能修改,呵呵。
说到它就不得不提InheritedModel,它是InheritedWidget的子类,InheritedModel可以做到部分数据改变的时候才会重建
推荐阅读
inheritedmodel-vs-inheritedwidget
https://juju.one/inheritedwidget-inheritedmodel/
InheritedWidget 缺点
- 容易造成不必要的刷新
- 不支持跨页面(route)的状态
- 数据是不可变的,必须结合StatefulWidget、ChangeNotifier或者Steam使用
Notification
它是Flutter中跨层数据共享的一种机制,注意,它不是widget,它就是它,不一样的傻叉,它提供了dispatch方法,来让我们沿着context对应的Element节点向上逐层发送通知
推荐阅读
flutter-notifications-bubble-up-and-values-go-down
notification
Notification缺点
- 不支持跨页面(route)的状态
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的状态管理有了一个重新的认识呢?如果对你有帮住,请点一下下面的赞哦。谢谢。