Flutter 学习之旅(二十九) 数据共享 InheritedWidget

InheritedWidget

是Flutter 中非常重要的一个功能性组件,他的主要作用是用来提供一种从上到下的一种传递数据的方式,
在flutter app中的theme 和 local 就是这样写的,

didChangeDependencies

这个方法在前面介绍StatefulWidget 的时候介绍过该方法,表示他所依赖的父布局发现变化时这个方法会被framework 调用,但是如何判断父控件是否发生变化,就是根据 子布局是否使用并注册了父布局的 InheritedWidget,这里为什么说是使用并注册,我们会在下面介绍一下,

先写一个widget , 他含有一个count 属性,

class TsmShareInteritedWidget extends InheritedWidget {


  TsmShareInteritedWidget({@required this.data, Widget child}) : super(child: child);

  int data;

  @override
  bool updateShouldNotify(TsmShareInteritedWidget oldWidget) {
    return oldWidget.data != data;
  }


  static TsmShareInteritedWidget of(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType();
  }
}

想要使用 TsmShareInteritedWidget 共享出来的count 这个数据的使用方法就是

TsmShareInteritedWidget.of(context).data;

写一个TsmInheritedWidget 使用共享数据

class  TsmInheritedWidget extends StatefulWidget{
  @override
  State createState() =>_TsmInheritedWidgetState();

}


class _TsmInheritedWidgetState extends State{
  @override
  Widget build(BuildContext context) {
    printString('build');
    return Text(TsmShareInteritedWidget.ofDate(context).data.toString());
  }



  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    printString('didChangeDependencies');
  }

}

按照上面我的对InheritedWidget 这个控件介绍,他的作用是从上至下传递,也就是 InheritedWidget所共享的这个count 只能由他的子widget使用,最后再来一个使用他的例子

class _TsmInheritedSendPageState extends State {
  int count = 0;

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text('Inherited 学习'),
        ),
        body: Container(
          child: Center(
              child: TsmShareInteritedWidget(
            data: count,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                TsmInheritedWidget(),
                SizedBox(
                  height: 15,
                ),
                RaisedButton(
                  child: Text('计数器'),
                  onPressed: () {
                    setState(() {
                      count++;
                    });
                  },
                )
              ],
            ),
          )),
        ),
      );
}

点击按钮的时候发现didChangeDependencies 和build 方法同时打印

那么为什么非要多此一举监听didChangeDependencies,值机监听build方法不就可以了,其实如果数据发生变化是触发请求接口的条件,如果该条件放在build中,则请求接口会发发生的很频繁,而didChangeDependencies 则是可控制的变成,更为合理,(即didChangeDependencies 调用 build必定调用,但是build调用 didChangeDependencies 不一定被调用)

那么这里如果我只想引用数据不想要didChangeDependencies 回调被调用呢,
按照上面的说法是,如果想要didChangeDependencies 这个回调被调用必须是使用数据并注册,
也就是说我们不注册就可以了,

这里看了一下源码

  @override
  T dependOnInheritedWidgetOfExactType({Object aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

源码的逻辑就是先找到InheritedElement ,然后再做数据变化依赖,dependOnInheritedElement

通过搜索final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; 这句代码找到另一个只是获取数据widgit的方法,他只有获取数据的方法,并没有注册

  @override
  InheritedElement getElementForInheritedWidgetOfExactType() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    return ancestor;
  }

下面我们在TsmShareInteritedWidget 这添加一个ofData方法,修改TsmInheritedWidget 中的 TsmShareInteritedWidget.of 改成TsmShareInteritedWidget.ofData ,

  static TsmShareInteritedWidget ofDate(BuildContext context) {
    return context
        .getElementForInheritedWidgetOfExactType()
        .widget;
  }

修改后的TsmShareInteritedWidget 变化为

class TsmShareInteritedWidget extends InheritedWidget {


  TsmShareInteritedWidget({@required this.data, Widget child}) : super(child: child);

  int data;

  @override
  bool updateShouldNotify(TsmShareInteritedWidget oldWidget) {
    return oldWidget.data != data;
  }

  ///  源码中方法
  ///  获取数据同时绑定
  ///
  ///  @override
  ///   T dependOnInheritedWidgetOfExactType({Object aspect}) {
  ///     assert(_debugCheckStateIsActiveForAncestorLookup());
  ///     final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  ///     if (ancestor != null) {
  ///       assert(ancestor is InheritedElement);
  ///       return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  ///     }
  ///     _hadUnsatisfiedDependencies = true;
  ///     return null;
  ///    }
  ///
  ///
  static TsmShareInteritedWidget of(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType();
  }

  /// 只获取数据
  ///
  ///
  ///
  ///源码中的方法
  ///  @override
  ///  InheritedElement getElementForInheritedWidgetOfExactType() {
  ///    assert(_debugCheckStateIsActiveForAncestorLookup());
  ///    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  ///    return ancestor;
  ///  }
  static TsmShareInteritedWidget ofData(BuildContext context) {
    return context
        .getElementForInheritedWidgetOfExactType()
        .widget;
  }
}

///  总结,通过查看源码发现,dependOnInheritedWidgetOfExactType  依赖关系真正建立的时机是  dependOnInheritedElement  这个方法,除了这个,其实这两个方法都是一样的
///
///   这就实现了使用   dependOnInheritedWidgetOfExactType  该方法时,不仅可以使用数据,同时在数据发生变更的同时,didChangeDependencies  方法还是调用
///   而  getElementForInheritedWidgetOfExactType  这个方法只能使用数据,
///
///   其实在使用过程中还会发生 如果  didChangeDependencies  方法发生变化,其实build 方法也会被调用,费什么非要多此一举呢,其实如果数据发生变化是触发请求接口的条件
///   如果该条件放在build中,则请求接口会发发生的很频繁,而didChangeDependencies 则是可控制的变成,更为合理
///
///

这里reload代码发现 didChangeDependencies 还是会打印日志,重新打包后就不打印,貌似就是在重新reload的时候,已经注册的方法,没办法取消注册导致的

其实现在这种用法还是有弊端的,那就是我们只想更新一个TsmInheritedWidget 中的数据,但是调用setState方法时,整个页面都刷新了,这样做不是很好,这个问题我会在下一篇Provider中解决

我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

你可能感兴趣的:(Flutter 学习之旅(二十九) 数据共享 InheritedWidget)