flutter学习-状态State管理
Posted GY-93
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flutter学习-状态State管理相关的知识,希望对你有一定的参考价值。
状态State管理
状态管理是声明式编程非常重要的一个概念,我们前面是介绍过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;
//2.定义构造方法
GYInheritedWidget({required this.counter, required Widget child}) : super(child: child);
//3.我们习惯定义一个of方法来获取这个对象: 获取组件最近的当前的InheritedWidget对象
static GYInheritedWidget? of(BuildContext context) {
//该方法的作用:沿着Element树,去找最近的GYInheritedWidget,从Element中取出Widget对象
return context.dependOnInheritedWidgetOfExactType();
}
//4。重写父类的抽象方法,该方法的作用是决定要不要调用State中didChangeDependencies方法(前面学习声明周期方法时学习过)
// true: 执行当前依赖InheritedWidget的State中的didChangeDependencies方法;false:表示不调用该方法
@override
bool updateShouldNotify(GYInheritedWidget oldWidget) {
// TODO: implement updateShouldNotify
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';
//main函数作为程序的入口
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(
//如何使用共享数据模型: 需要再共享的widget的外层,使用共享数据模型包裹
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
更新
//1.创建ChangeNotifier 来保存共享数据
//class GYDataChangerNotifier with ChangeNotifier
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作为顶层,不管在那里都可以访问这个共享数据
ChangeNotifierProvider(create: (ctx) {
return GYDataChangerNotifier();
}, child: MyApp(),)
);
}
第三步:在需要使用共享数据的地方访问数据(显示数据)
class GYShowData01 extends StatelessWidget {
@override
Widget build(BuildContext context) {
//访问共享数据 static T of<T>(BuildContext context, {bool listen = true})
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) {
//访问共享数据 static T of<T>(BuildContext context, {bool listen = true})
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()
],
),
),
/*
Consumer({
Key? key,
required this.builder,
Widget? child,
}) : super(key: key, child: child);
//build类型
final Widget Function(
BuildContext context,
T value,
Widget? child,
) builder;
*/
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方法执行");
//访问共享数据 static T of<T>(BuildContext context, {bool listen = true})
//int counter = Provider.of<GYDataChangerNotifier>(context).counter;
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方法执行");
//访问共享数据 static T of<T>(BuildContext context, {bool listen = true})
//int counter = Provider.of<GYDataChangerNotifier>(context).counter;
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
/*
Selector({
Key? key,
required ValueWidgetBuilder<S> builder,
required S Function(BuildContext, A) selector,
ShouldRebuild<S>? shouldRebuild,
Widget? child,
})
*/
floatingActionButton: Selector<GYDataChangerNotifier, GYDataChangerNotifier>(
builder: (ctx, model, child) {
print("floatingActionButton build方法");
return FloatingActionButton(
child: child,
onPressed: () {
//修改共享数据
model.counter += 1;
},
);
},
selector: (ctx, modelA) {
return modelA; //把A转换成S
},
shouldRebuild: (pre, next) {
return false; //表示需不需要从新构建(build),true:表示需要重新构建,会执行build方法, 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作为顶层,不管在那里都可以访问这个共享数据
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
以上是关于flutter学习-状态State管理的主要内容,如果未能解决你的问题,请参考以下文章
Flutter漫说:组件生命周期State状态管理及局部重绘的实现(Inherit)
Flutter 报错 DioError [DioErrorType.DEFAULT]: Bad state: Insecure HTTP is not allowed by platform(代码片段
Flutter Stateful Widget 重新创建 State
Flutter 组件的生命周期State 管理及局部重绘 | 开发者说·DTalk