# 使用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;    }  ...

下面配上一张流程图来加深一下印象:

# 总结

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