状态管理是声明式编程非常重要的一个概念,我们前面是介绍过Flutter是声明编程的,状态管理也是区分声明式编程和命令编程的区别
这里我们就来学习下声明式编程的状态管理
1. 为什么需要状态管理
1.1 认识状态管理
很多从命令式编程框架(Android和IOS原生开发者)转成声明式编程(Flutter、Vue、React等)刚开始并不是适应,因为需要换一个角度来考虑APP的开发模式。
Flutter作为一个现代的框架,是声明式编程的: 
上图是Flutter应用的构建过程
在编写一个应用的过程中,我们有大量的State需要进行管理,而正是对这些State的改变,来更新界面的刷新 
1.2 不同的状态管理分类
1.2.1 短时状态(Ephemeral state)
某些状态只需要在自己的widget中使用即可
- 比如我们之前做的简单的计数器案例(counter)
- 比如一个PageView组件记录当前的页面
- 比如一个动画记录当前的进度
- 比如一个BottomNavigationBar中当前被选中的tab
这种状态只需要我们使用StatefulWidget对应的State类自己管理即可,Widget树中其它部分并不需要访问这个状态。
这种方式在之前的学习中,我们已经使用很多次,相信我们已经很熟悉了。
1.2.2 应用状态App State
开发中也有非常多的状态需要再多个地方进行共享
- 比如用户的个性化选项
- 比如用户的登录状态信息
- 比如一个电商的购物车信息
- 比如一个新闻应用的已读消息或则未读消息
这种状态我们如果在Widget之间传递来、传递去、那是无穷尽的,并且代码的耦合度会变的非常高,牵一发而动全身,无论是编写代码质量、后期维护、可扩展性都非常差。
这个时候我们可以选择全局状态管理的方式,来对状态进行统一的管理和应用。
1.2.3如何选择不同的管理方式
开发中没有明确的规则去区分哪些是短时状态、哪些是应用状态
但是我们可以遵守下面这幅图的流程: 
关于我们选择哪种管理状态方式更好,采取原则:选择能够减少麻烦的方式
2. 共享状态
2.1 InheritedWidget
InheritedWidget和React中的context功能类似,可以实现跨组件数据的传递。
定义一个共享数据的InheritedWidget ,需要继承自InheritedWidget
class GYInheritedWidget extends InheritedWidget {
final int counter;
GYInheritedWidget({required this.counter, required Widget child}) : super(child: child);
static GYInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
@override
bool updateShouldNotify(GYInheritedWidget oldWidget) {
return oldWidget.counter != counter;
}
}
- 这里定义一个
of 方法方法,该方法是通过context 开始查找祖先GYInheritedWidget (可以查看源码查找过程)
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
updateShouldNotify 方法是对比新旧GYInheritedWidget ,是否需要对更新相关依赖的Widget
完整示例代码:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hello_flutter/http/http_dio_tools.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _counter = 100;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GYInheritedWidget(
counter: _counter,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GYShowData01(),
GYShowData02()
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
setState(() {
_counter ++;
});
},
),
)
);
}
}
class GYShowData01 extends StatefulWidget {
@override
_GYShowData01State createState() => _GYShowData01State();
}
class _GYShowData01State extends State<GYShowData01> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("执行了_GYShowData01State中的didChangeDependencies");
}
@override
Widget build(BuildContext context) {
GYInheritedWidget? inheritedWidget = GYInheritedWidget.of(context);
int counter = 0;
if (inheritedWidget != null) {
counter = inheritedWidget.counter;
}
return Card(
color: Colors.red,
child: Text("当前计数: $counter", style: TextStyle(fontSize: 30),),
);
}
}
class GYShowData02 extends StatelessWidget {
@override
Widget build(BuildContext context) {
GYInheritedWidget? inheritedWidget = GYInheritedWidget.of(context);
int counter = 0;
if (inheritedWidget != null) {
counter = inheritedWidget.counter;
}
return Container(
color: Colors.blue,
child: Text("当前计数: $counter", style: TextStyle(fontSize: 30),),
);
}
}
2.2 Provider
Provide是目前官方推荐的全局状态管理工具,由社区作者Remi Rousselet 和 Flutter Team共同编写。
在使用之前,我们需要先引入对它的依赖,截止目前,Provider的版本为6.0.0 
2.2.1 Provider的基本使用
在使用Provider的时候,我们主要关心三个概念:
ChangeNotifier :真正数据(状态)存放的地方ChangeNotifierProvider :Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier Consumer :Widget树中需要使用数据(状态)的地方
我们用一个简单的计数器案例,来使用Provider来实现
第一步:创建自己的ChangeNotifier
我们需要一个ChangeNotifier来保存我们的状态,所以创建它
- 这里我们可以使用继承自
ChangeNotifier ,也使用使用with 关键字进行混入,这取决去是否需要继承自其它的类 - 我们使用一个私有的
_counter 并且提供了getter和setter - 在setter方法中我们监听到
_counter 改变,就调用notifiyListeners 方法,通知所有的Consumer 更新
class GYDataChangerNotifier extends ChangeNotifier {
int _counter = 100;
int get counter => _counter;
set counter(int value) {
_counter = value;
notifyListeners();
}
}
第二步:在Widget tree中插入ChangeNotifierProvider
我们需要再Widget tree中插入ChangeNotifierProvider,以便Consumer 可以获取到数据
- 将ChangeNotifierProvider放到了顶层,这样方便在整个应用的任何地方可以使用CounterProvider
void main() {
runApp(
ChangeNotifierProvider(create: (ctx) {
return GYDataChangerNotifier();
}, child: MyApp(),)
);
}
第三步:在需要使用共享数据的地方访问数据(显示数据)
class GYShowData01 extends StatelessWidget {
@override
Widget build(BuildContext context) {
int counter = Provider.of<GYDataChangerNotifier>(context).counter;
return Container(
color: Colors.blue,
child: Text("当前计数: $counter", style: TextStyle(fontSize: 30),),
);
}
}
class GYShowData02 extends StatelessWidget {
@override
Widget build(BuildContext context) {
int counter = Provider.of<GYDataChangerNotifier>(context).counter;
return Container(
color: Colors.blue,
child: Text("当前计数: $counter", style: TextStyle(fontSize: 30),),
);
}
}

** 第四部:点击加好,需要修改数据(状态)**
- 在floatingActionButton中使用Consumer,当点击按钮时,修改CounterNotifier中的counter数据;
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GYShowData01(),
GYShowData02()
],
),
),
floatingActionButton: Consumer<GYDataChangerNotifier>(
builder: (ctx, model,child){
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
model.counter += 1;
},
);
},
),
)
);
}
}
Consumer的build方法解析:
- 参数一:context,每个build方法都会有上下文,目的是知道当前树的位置
- 参数二:ChangeNotifier对应的实例,也是我们在builder函数中主要使用的对象
- 参数三:child,目的是进行优化,如果builder下面有一颗庞大的子树,当模型发生改变的时候,我们并不希望重新build这颗子树,那么就可以将这颗子树放到Consumer的child中,在这里直接引入即可(注意我案例中的Icon所放的位置)

2.2.2 Provider.of的弊端
事实上,因为Provider是基于inheritedWidget,所以我们在使用ChangeNotifier中的数据时,我们可以通过Provider.of的方式时,例如下面代码:
Text("当前计数: ${Provider.of<GYDataChangerNotifier>(context).counter}
我们可以发现这种方式很简洁,那么在开发中我们是否需要选择这种方式?
- 答案是否定的,更多的时候我们还是选择
Consumer 的方式
为什么呢?因为Consumer在刷新整个Widget树时,会尽可能少的rebuild Widget。
来看下Provider.of 和Consumer 的代码结果比较:
class GYShowData01 extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("GYShowData01 build方法执行");
return Container(
color: Colors.blue,
child: Text("当前计数: ${Provider.of<GYDataChangerNotifier>(context).counter}", style: TextStyle(fontSize: 30),),
);
}
}
class GYShowData02 extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("GYShowData02 build方法执行");
return Container(
color: Colors.blue,
child: Consumer<GYDataChangerNotifier>(builder: (ctx, model, child){
return Text("当前计数: ${model.counter}", style: TextStyle(fontSize: 30));
})
);
}
}

根据结果比较可以得出如下结论:
Provider.of : 当Provider中的数据发生改变时, Provider.of所在的Widget整个build方法都会重新构建Consumer :整个Widget的build方法不会执行,只会执行Consumer 的build方法,这样就减少了不必要的build方法的执行,性能更高
2.2.3 Selector的选择
Consumer是否是最好的选择? 并不是,consumer也会存在弊端
- 比如上述代码点击了floatingActionButton时,我们发现floatingActionButton位置重新build了,但是该位置有从新build的必要吗? 没有,因为它只是在操作数据,并没有展示
- 如何做到让它不要重新build?使用Selector来代替Consumer
floatingActionButton: Selector<GYDataChangerNotifier, GYDataChangerNotifier>(
builder: (ctx, model, child) {
print("floatingActionButton build方法");
return FloatingActionButton(
child: child,
onPressed: () {
model.counter += 1;
},
);
},
selector: (ctx, modelA) {
return modelA;
},
shouldRebuild: (pre, next) {
return false;
},
child: Icon(Icons.add),
)
- Selector:
- selector方法(作用,对原有的数据进行转换)
- shouldRebuild(作用,要不要重新构建)
- Selector和Consumer对比,不同之处主要是三个关键点:
- 关键点一:
- 泛型参数一:我们这次要使用的Provider
- 泛型参数二:转换之后的数据类型,比如我这里转换之后依然是使用GYDataChangerNotifier,那么他们两个就是一样的类型
- 关键点2:selector回调函数
- 转换的回调函数,你希望如何进行转换
S Function(BuildContext, A) selector - 我这里没有进行转换,所以直接将A实例返回即可
- 关键点3:是否希望重新rebuild
- 这里也是一个回调函数,我们可以拿到转换前后的两个实例
bool Function(T previous, T next); - 因为这里我不希望它重新rebuild,无论数据如何变化,所以这里我直接return false

这个时候我们重新运行程序,点击按钮,发现floatingActionButton中的代码并不会重新的build ,所以在某些时候我们可以使用Selector 来代替Consumer
2.2.4 MultiProvider
在开发中,我们需要共享的数据肯定不止一个,并且数据之间我们需要组织到一起,所以一个Provider必然是不够的。
那在开发中我们有多个Provider需要提供应该怎么办?
这里我就不创建新的Provider了,直接使用上面的Provider来演示
方法一:多个Provider之间的嵌套:
void main() {
runApp(
ChangeNotifierProvider(
create: (ctx) {
return GYDataChangerNotifier();
},
child: ChangeNotifierProvider(
create: (ctx) {
return GYDataChangerNotifier();
},
child: MyApp(),
),
));
}
不过这种做法,有大的弊端,如果嵌套的层级过多,就不方便维护,扩展性也比较差
方法二: 使用MulitProiver
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider(create: (ctx) => GYDataChangerNotifier()),
ChangeNotifierProvider(create: (ctx) => GYDataChangerNotifier()),
ChangeNotifierProvider(create: (ctx) => GYDataChangerNotifier())
]));
}
如果同一个地方需要访问多个不同Provider的数据,那怎么办了?可以嵌套,但是这里Provider给我们提供了一个Consumer2 这样一个类,可以同时传入两个数据 
相对的还有Consumer3 一直到Consumer6
|