官网关于状态管理的一段介绍:
Managing state
官方视频介绍:Inherited Widgets Explained - Flutter Widgets 101 Ep. 3
在Flutter的Widget树中,如果树中的底层Widget想要获取到一些上层的状态数据,通过build方法里的Widget构造函数传入数据是可行的一种,但是当层级较多时,那一直从上向下传递非常麻烦。
Flutter内置了一种依赖注入的方式或者说是一种全局提供数据的方式:Inherited Widgets
这里的Inherit并不是指的类class的继承,而是指的Widget层次结构上的继承,
一个继承自InheritedWidget的例子:
class InheritedContext extends InheritedWidget {
//数据
final InheritedTestModel inheritedTestModel;
//点击+号的方法
final Function() increment;
//点击-号的方法
final Function() reduce;
InheritedContext({
Key key,
@required this.inheritedTestModel,
@required this.increment,
@required this.reduce,
@required Widget child,
}) : super(key: key, child: child);
static InheritedContext of(BuildContext context) {
return context.inheritFromWidgetOfExactType(InheritedContext);
}
//是否重建widget就取决于数据是否相同
@override
bool updateShouldNotify(InheritedContext oldWidget) {
return inheritedTestModel != oldWidget.inheritedTestModel;
}
}
在底层的Widget中获取上层某个Widget中的数据,可以使用InheritedContext.of(InheritedContext)
,这时候Flutter就会从当前的context开始,逐层往上,直到找到Widget树中包含和定义的InheritedWidget类型一致的Widget,然后获取数据。
我们通常使用的Theme.of(context).primaryColor 就是这样的一个例子。Theme也是一个InheritedWidget
⚠️注意:
- InheritedWidget是不可变的immutable,所以其中的数据或者对外提供的服务都会被标记为final。所以也不应该存在给该数据重新赋值的情况。
- But the fact that something is final only means it can’t be reassigned, It does not mean it cannot change internally.
比如这里的InheritedWidget中的一个value换成一个service,这个service可以是一个数据库的包装类对象、一个Web API的代理类对象、或者是一个本地资源的提供者,这个service对象可以有自己的内部状态,可以调用这个service的方法或者订阅一个stream流,如下代码所示:
InheritedNose.of(context).service.blow();
StreamBuilder(
stream:InheritedNose.of(context).service.sneezes,
builder: /** **)
使用InheritedWidget的正确姿势
-
在Widget子树中,如果哪个Widget中调用了XXXXWidget.of(context).data方法来获取InheritedWidget中的data,那么当前的这个子Widget便和InheritedWidget产生了
依赖关系
,当InheritedWidget中的data发生变化的时候,会调用InheritedWidget的
updateShouldNotify()
方法,如果该方法返回了true,那么Flutter Framework就会调用产生订阅关系的子Widget的didChangeDependencies()
方法; -
按照上面的理解,当Widget子树中,没有产生订阅关系的Widget在InheritedWidget中大data发生变化时,是不应该被重新创建的才对
这里引用了使用InheritedWidget被忽略的要点一文中的例子代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to InheritedWidget',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.yellow,
primaryColor: Colors.white,
),
// home: MyHomePage(title: 'Flutter Demo Home Page'),
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MyState();
}
}
class MyState extends State<MyWidget> {
int tmpData = 0;
@override
Widget build(BuildContext context) {
print('MyWidget build');
return Scaffold(
appBar: AppBar(title: Text('Welcome to InheritedWidget')),
// home: MyHomePage(title: 'Flutter Demo Home Page'),
body: MyInherited(
data: tmpData,
child: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
WidgetA(),
WidgetB(),
FlatButton(
child: Text("Click me"),
onPressed: () {
setState(() {
print('onPressed');
tmpData += 1;
});
},
),
],
),
)),
),
);
}
}
class MyInherited extends InheritedWidget {
final int data;
MyInherited({this.data, Widget child}) : super(child: child) {
print('MyInherited construct');
}
@override
bool updateShouldNotify(MyInherited oldWidget) {
bool result = oldWidget.data != this.data;
print('MyInherited updateShouldNotify result = $result');
return result;
}
static MyInherited of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInherited);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('WidgetA build');
int data = MyInherited.of(context).data;
return Text('WidgetA data = $data');
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('WidgetB build');
return Text('WidgetB');
}
}
自定义一个Widget MyInherited
继承自InheritedWidget
,里面包含了WidgetA和WidgetB和一个按钮,当点击按钮的时候,MyInherited中的数据data变化,按照我们上面的理解,理论上这时候应该只调用WidgetA的build方法,但是输出结果显示,WidgetB的方法也被调用了:
I/flutter ( 6912): onPressed
V/AudioManager( 6912): playSoundEffect effectType: 0
V/AudioManager( 6912): querySoundEffectsEnabled...
I/flutter ( 6912): MyWidget build
I/flutter ( 6912): MyInherited construct
I/flutter ( 6912): MyInherited updateShouldNotify result = true
I/flutter ( 6912): WidgetA build
I/flutter ( 6912): WidgetB build
更详细的介绍可以参考
《Flutter实战》
7.2 数据共享(InheritedWidget)