前言
2019 Google I/O 大会,google 就推出 Provider,成为官方推荐的状态管理方式之一,Flutter 状态管理一直是个很热门的话题,而且状态管理的库也是超级多,这确实是我们每一个做 Flutter 开发难以避免的一道坎,既然这么重要,我们如何去理解它,如何使用它,如何做到更好呢?接下来让我告诉你答案
啰嗦几句
该文章已经经历了一周的迭代,预计还要一周左右,要做一个全面的分析,当然要每个细节都要关注到,如果您觉得好,请不要吝啬您的大拇指,顺便点个赞哦,么么哒,如果有不对的地方提出来,一个地方一个红包奖励哦,爱你们。
主要内容
一张图告诉你,我要讲的主要内容。下面将围绕这八个方面来讲。七个理论,一个实践。
- 状态管理是什么
- 为什么需要状态管理
- 状态管理基本分类
- 状态管理的底层逻辑
- 状态管理的使用原则
- 使用成熟状态管理库的弊端
- 选择状态管理库的原则
- Provider 深入分析(学以致用)
状态管理是什么
我们知道最基本的程序是什么:
- 程序 = 算法 + 数据结构
数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿了整个程序世界,首先二者表现为不可分割的关系。其实 Flutter 不就是一个程序吗,那我们面临的最底层的问题还是算法和数据结构,所以我们推导出
- Flutter= 算法 + 数据结构
那状态管理是什么?我也用公式来表达一下,如下:
- Flutter 状态管理 = 算法 + 数据结构 +UI 绑定
瞬间秒懂有没有?来看一个代码例子:
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(),},
);
})
- 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 才是最主要的,这个时候一大堆的状态管理就出来了,当然状态管理也不是仅仅为了解决更新问题。
我再抛出一个问题,如果我有一个 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 函数来变更,没什么问题对吧,而且功能实现了,但你仔细思考一下,这有什么问题呢?
- _WidgetAState 被全局化,而且它所有状态被暴漏出去,如果_WidgetAState 有十个状态,只有一个想让别人变更,可惜已经晚了,你加 ’_’ 也不行,组件的隐私全没了
- 耦合变高,WidgetB 有_WidgetAState 的强关联,我们编码追求的解偶,在这里完全被忽视了
- 性能变差,为什么这么说?因为每次_widgetAState.setState 都会导致整个页面甚至子 Widget 的重新 build,如果_widgetAState 里面有成千上百的状态,性能肯定差到极点
- 不可测,程序变得难以测试
如何变好呢
这就需要选择一种合适的状态管理方式。
状态管理的目标
其实我们做状态管理,不仅仅是因为它的特点,而为了更好架构,不是吗?
- 代码要层次分明,易维护,易阅读
- 可扩展,易维护,可以动态替换 UI 而不影响算法逻辑
- 安全可靠,保持数据的稳定伸缩
- 性能佳,局部优化
这些不紧紧是状态管理的目的,也是我们做一款优秀应用的基础架构哦。
基本分类
- 局部管理 官方也称 Ephemeral state,意思是短暂的状态,这种状态根本不需要做全局处理
举个例子,如下方的_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 ...
);
}
}
- 全局管理 官方称 App state,即应用状态,非短暂状态,您要在应用程序的许多部分之间共享,以及希望在用户会话之间保持的状态,就是我们所说的应用程序状态(有时也称为共享状态)
例如:
- 用户偏好
- 登录信息
- 购物车
- 新闻阅读状态
状态分类官方定义
没有明确的通用规则来区分特定变量是短暂状态还是应用程序状态。有时,您必须将一个重构为另一个。例如,您将从一个明显的短暂状态开始,但是随着您的应用程序功能的增长,可能需要将其移至应用程序状态。出于这个原因,请使用下图进行分类:
总之,任何 Flutter 应用程序中都有两种概念性的状态类型。临时状态可以使用 State 和 setState()来实现,并且通常是单个窗口小部件的本地状态。剩下的就是您的应用状态。两种类型在任何 Flutter 应用程序中都有自己的位置,两者之间的划分取决于您自己的喜好和应用程序的复杂性
没有最好的管理方式,只有最合适的管理方式
底层逻辑
底层逻辑我想告诉你的是,Flutter 中目前有哪些可以做到状态管理,有什么缺点,适合做什么不适合做什么,只有你完全明白底层逻辑,才不会畏惧复杂的逻辑,即使是复杂的逻辑,你也能选择合理的方式去管理状态。
- 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,同样是通过 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 的局部刷新,不会导致整个页面的重建,是不是就好很多了呢?
State 缺点
从上面的代码我们分析一下它的缺点
- 无法做到跨组件共享数据(这个跨是无关联的,如果是直接的父子关系,我们不认为是跨组件)
setState 是 State 的函数,一般我们会将 State 的子类设置为私有,所以无法做到让别的组件调用 State 的 setState 函数来刷新
- setState 会成为维护的难点,因为啥哪哪都是。
随着页面状态的增多,你可能在调用 setState 的地方会越来越多,不能统一管理
- 处理数据逻辑和视图混合在一起,违反代码设计原则
比如数据库的数据取出来 setState 到 Ui 上,这样编写代码,导致状态和 UI 耦合在一起,不利于测试,不利于复用。
State 小结
当然反过来讲,不是因为它有缺点我们就不使用了,我们追求的简单高效,简单实现,高效运行,当复杂到需要更好的管理的时候再重构。一个基本原则就是,状态是否需要跨组件使用,如果需要那就用别的办法管理状态而不是 State 管理。
InheritedWidget
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);
}
}
- InheritedWidgetDemo 共享状态 accountId 给了 MyOtherWidget,而 MyOtherWidget 是 MyWidget 的子 Widget,这就是 InheritedWidget 的功效,它可以做到跨组件共享状态。
- const MyWidget() 表示该 Widget 是常量,不会因为页面的刷新导致重新 build,这就是优化的细节,这里想一下,如果你用 State 实现,不是就需要它 setState 才能实现 MyOtherWidget 的重新 build,这样做的坏处就是导致整个 UI 的刷新。
- updateShouldNotify 它也是一个优化点,在你横屏变竖屏的同时,导致整个 UI 重新 build,可由于 updateShouldNotify 的判断,系统将不会重新 build MyOtherWidget,也是一种布局优化。
- 子树中的组件通过 InheritedWidgetDemo.of(context)访问共享状态。
有的人想了,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/ 欢迎阅读学习哦
- _MyInherited 是 InheritedWidget,每次我们通过单击“Widget A”的按钮添加元素时都会重新创建
- MyInheritedWidget 是一个状态为包含元素列表的窗口小部件。可通过(BuildContext 上下文)的静态 MyInheritedWidgetState 访问此状态
- MyInheritedWidgetState 公开一个 getter(itemsCount)和一个方法(addItem),以便子控件树的一部分的控件可以使用它们
- 每次我们向 State 添加元素时,都会重新构建 MyInheritedWidgetState
- MyTree 类仅构建一个小部件树,将 MyInheritedWidget 作为该树的父级
- WidgetA 是一个简单的 RaisedButton,按下该按钮时,会调用最近的 MyInheritedWidget 的 addItem 方法。
- WidgetB 是一个简单的 Text,它显示在最接近的 MyInheritedWidget 级别上显示的元素数量
看了一下日志输出如图:
有没有发现一个问题?当 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 缺点
通过上面的分析,我们来看下它的缺点
- 容易造成不必要的刷新
- 不支持跨页面 (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
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 的状态管理有了一个重新的认识呢?如果对你有帮住,请点一下下面的赞哦。谢谢?。