# 使用InheritedWidget传递数据
- 2019 年 10 月 6 日
- 筆記
# 使用InheritedWidget传递数据
除了StatefulWidget、StatelessWidget之外flutter还提供了另外一个用的Widget组件即InheritedWidget。那么该组件的作用时什么呢?
# 我们来看一下数据是如何从父widget传递到子widget的
下面我们定义一个嵌套三层的数据传递例子:
class DataTransferAWidget extends StatelessWidget { final int data; DataTransferAWidget(this.data); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Expanded(child: Text("A:${data.toString()}")), Expanded( //子组件依赖自己的data数据 child: DataTransferBWidget(data), ) ], ), ); } } class DataTransferBWidget extends StatelessWidget { final int data; DataTransferBWidget(this.data); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Expanded(child: Text("B:${data.toString()}")), //子组件依赖自己的data数据 Expanded(child: DataTransferCWidget(data)) ])); } } class DataTransferCWidget extends StatelessWidget { final int data; DataTransferCWidget(this.data); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Text("C:${data.toString()}")); } } class DataTransferDemoPage extends StatefulWidget { @override State<StatefulWidget> createState() { return _DataTransferDemoPageState(); } } class _DataTransferDemoPageState extends State<DataTransferDemoPage> { //定义一个待向子widget传递的数据 int data = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("数据传递"), ), body: Container( alignment: Alignment.center, //向子传递data child: DataTransferAWidget(data)), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { setState(() { data++; }); }), ); }
上面例子你看到,每个DataTransferWidget
的构造函数都依赖父widget的data,如果还有第4层,第5层…等嵌套的话,data要不停的通过构造函数传递,甚是麻烦。
既然每层widget依赖的都是根的data,那么为什么不定义一个全局静态变量来做呢?(好想法,我们试一下)
# 使用static代替构造函数传递数据
typedef ChildWidgetBuilder =DataTransferCWidget Function(int); class DataTransferAWidget extends StatelessWidget { //下面代码已经没有意义了 //final int data; //DataTransferAWidget(this.data); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Expanded(child: Text("A:${DataModel.data.toString()}")), Expanded( child: DataTransferBWidget(), ) ], ), ); } } class DataTransferBWidget extends StatelessWidget { //下面代码已经没有意义了 //final int data; //DataTransferBWidget(this.data); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Expanded(child: Text("B:${DataModel.data.toString()}")), Expanded(child: DataTransferCWidget()) ])); } } class DataTransferCWidget extends StatelessWidget { //下面代码已经没有意义了 //final int data; //DataTransferCWidget(this.data); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Text("C:${DataModel.data.toString()}")); } } //静态变量 class DataModel{ static int data=0; } class DataTransferDemoPage extends StatefulWidget { @override State<StatefulWidget> createState() { return _DataTransferDemoPageState(); } } class _DataTransferDemoPageState extends State<DataTransferDemoPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("数据传递"), ), body: Container( alignment: Alignment.center, child: DataTransferAWidget()), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { setState(() { DataModel.data++; }); }), ); } }
全局static类型的变量是不是更方便一些呢?!
# 该是InheritedWidget出场的时候了
flutter官方api是这样说的:有效地在树中传播信息的小部件的基类,下面咱们来看一下它的定义:
//我们可以看到该类是一个抽象类 abstract class InheritedWidget { //这是一个常量构造函数,常量的作用就是一旦编译后就不可能再被修改 const InheritedWidget({ Key key, Widget child }) : super(key: key, child: child); //这个函数不在我们的讨论范围之内 @override InheritedElement createElement() => InheritedElement(this); //这个函数可以接收旧的对象,用来判断新旧InheritedWidget是否一样,它又什么作用呢?往下看... @protected bool updateShouldNotify(covariant InheritedWidget oldWidget); }
它是一个抽象类,这就意味着我们要去实现它:
class TestModel { int model = 0; void add() { model++; } } class MyInheriteWidget extends InheritedWidget { //这里我们定义一个我们准备向后代传递的数据 final DataModel model; //另外InheriteWidget需要包裹一个Widget //由于InheritedWidget仅用来传输数据, //所以这个child或者child的后代需要是一个StatelessWidget或StatefulWidget final Widget child; MyInheriteWidget({Key key, @required this.model, @required this.child}) : super(key: key, child: child); //这里我们要判断新旧widget是否相同 @override bool updateShouldNotify(MyInheriteWidget oldWidget) { return model != oldWidget.model; } static TestModel of(BuildContext context, [bool onChangeBuild = true]) { var widget = onChangeBuild ? context.inheritFromWidgetOfExactType(MyInheriteWidget) : context .ancestorInheritedElementForWidgetOfExactType(MyInheriteWidget) .widget; return (widget as MyInheriteWidget).model; } }
此时,一个可以用与向后台传递数据的InheritedWidget就完成了,下面来看如何使用:
class _InheriteWidgetDemoPage extends State<InheriteWidgetDemoPage> { //在一个有状态的widget中定义它需要维护的状态 var testModel = TestModel(); @override Widget build(BuildContext context) { //使用InheritedWidget向后代传递数据 return MyInheriteWidget( model: testModel, child: Scaffold( appBar: AppBar(title: Text("title")), //或许你想这样使用,这是错误的,我们应该获取的是InheritedWidget的数据而不是当前widget的 //body:Text(testModel.model.toString()) //为了更清楚,我们将获取InheritedWidget的数据放在一个单独的widget中执行,如下 body: TestAWidget(), floatingActionButton: Builder( builder: (context) => FloatingActionButton( child: Icon(Icons.add), onPressed: () { setState(() { MyInheriteWidget.of(context).add(); }); }, ), )), ); } } class TestAWidget extends StatelessWidget { TestAWidget(); @override Widget build(BuildContext context) { //官方文档提到我们应该这样获取InheritedWidget上传递的数据,是不是有点类似全局静态变量呢?! //不同的是 这是从context获取的,因为context可以贯穿整个widget依赖树,像android的context //官方给的demo不是下面这样 //var myInheritedWidget = context.inheritFromWidgetOfExactType(MyInheriteWidget) as MyInheriteWidget; //return Center(child: Text(myInheritedWidget.model.model.toString())); //应该是这样,将获取传递对象的代码放在MyInheritedWidget中,这仅是为了代码可读性 var model = MyInheriteWidget.of(context).model.toString(); return Center(child: Text(model)); } }
此时InheritedWidget向后代传递数据的方式已经完了,从现有的实现来说并没有比全局静态变量有什么优略之处,如果仅是这样也就没有它存在的价值了,请往下看…
# updateShouldNotify的作用
要弄清楚updateShouldNotify的作用我们还要翻一翻源码了,不然只看api文档还是一头雾水,上源码:
//我们知道一个widget由:widget,element,ReanderObject三部分组成 class InheritedElement extends ProxyElement { ... @override void updated(InheritedWidget oldWidget) { //通过字符串搜索你会找到该方法是在此处调用的 if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget); } @override void notifyClients(InheritedWidget oldWidget) { ... notifyDependent(oldWidget, dependent); } } @protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); } ... } class StatefulElement extends ComponentElement { @override void didChangeDependencies() { super.didChangeDependencies(); //调了这么一圈最后是为了调用didChangeDependencies方法 _state.didChangeDependencies(); } } abstract class ProxyElement extends ComponentElement{ @protected void updated(covariant ProxyWidget oldWidget) { notifyClients(oldWidget); } }
从上面重要的源码可以看到最后updateShouldNotify决定了didChangeDependencie()会不会被调用。
另外,分析源码的调用过程,也可以借助idea的debugger模式,在Frames里面我们可以看到代码是如何一步步被调用的。
# didChangeDependencie又是什么
didChangeDependencie是State中定义的一个回调函数,而State正是暴漏StatefulWidget生命周期的地方,我们可以同步实现State的不同方法,来对widget的生命周期变化时做出一些响应。
上面例子我们定义了一个无状态的TestAWidget来演示如果获取InheritedWidget要向子传递的数据,下面我们通过一个有状态的控件来展示在获取数据的同时响应didChangeDependencie的调用。
lass TestBWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _TestBWidgetState(); } } class _TestBWidgetState extends State<TestBWidget> { //这里就是要被调用的地方 @override void didChangeDependencies() { super.didChangeDependencies(); print('TestBWidget'); } @override Widget build(BuildContext context) { var model = MyInheriteWidget.of(context).model.toString(); return Center(child: Text(model)); } }
现在我们已经知道了updateShouldNotify返回true时didChangeDependencies()就会被调用。
InheritedWidget是为了向后代传递数据,如果InheritedWidget发生了嵌套呢?可能你只想响应某一个先辈的didChangeDependencies()调用,这也是可以,通过ancestorInheritedElementForWidgetOfExactType来获取先辈的数据,不会将自己的didChangeDependencies注册进该先辈的调用集合。
# 有选择性的注册didChangeDependencies调用
前面的代码中有一段是这样的:
static TestModel of(BuildContext context, [bool onChangeBuild = true]) { //通过一个变量来控制调用哪个方法获取先辈的数据 var widget = onChangeBuild ? context.inheritFromWidgetOfExactType(MyInheriteWidget) : context .ancestorInheritedElementForWidgetOfExactType(MyInheriteWidget) .widget; return (widget as MyInheriteWidget).model; }
要搞清楚inheritFromWidgetOfExactType与ancestorInheritedElementForWidgetOfExactType的区别,我认为看源代码是最直接的:
... @override InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) { final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; //重点再这里 if (ancestor != null) { return inheritFromElement(ancestor, aspect: aspect); } _hadUnsatisfiedDependencies = true; return null; } @override InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) { final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; return ancestor; } @override InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) { _dependencies ??= HashSet<InheritedElement>(); //将widget添加到依赖集合后就会收到didChangeDependencies调用 _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; } ...
下面配上一张流程图来加深一下印象:

# 总结
- InheritedWidget用与向后代传递/共享数据
- 通过updateShouldNotify方法可以控制是否要调用后代的didChangeDependencies方法
- didChangeDependencies被调用的前提是这个后代是一个StatefulWidget,且是通过inheritFromWidgetOfExactType方法来获取先辈的数据。