暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Flutter2.*_数据共享

燕山雷震子 2021-11-05
543

    最近因为flutter项目写的有些上头,所以关于flutter的更新会相对多一些。今天来分享一下flutter的数据共享组件inheritedwidget,它为flutter提供了一种在widget树中可以从上到下进行数据共享的选择,帮助我们实现在任意子widget中获取所有共享数据的诉求,最为常见的使用场景有共享主题和国际化的local处理。

    

didChangeDependencies


    

    提及didChangeDependencies就需要聊一下widget的生命周期,关于widget的生命周期可以大致分为:

    ·initstate:当widget第一次插入到widger树时被调用,对于每个state对象,flutter只调用一次该处理。

    ·didChangeDependencies:当state对象的依赖发生变化时被调用,如当前系统语言local或主题发生变化时,flutter框架层会通知widget调用该回调。

    ·build:用于构建widget子树。

    ·reassemle:热重载时调用。

    ·didUpdateWidget:widget重新构建时调用。

    ·deactivate:当state对象从树中移除时调用。

    ·dispose:state对象永久从树中移除时调用。

对于didChangeDependencies相关的依赖,实际就是子widget是否使用了父widget中的inheritedwidget的数据,使用即为有依赖关系,这种机制可以使子组件在所依赖的inheritedwidget发生变化时进行自身更新,让我们来看一下实例代码:

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  final int data; //需要在子树中共享的数据,保存点击次数

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static ShareDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    return old.data != data;
  }}

shareddatawidget继承inheritedwidget,使用data保存共享数据,然后实现子组件_testwidget,在其build方法中饮用shareddatawidget中的数据,在_testwidget的didChangeDependencies打印日志:

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => __TestWidgetState();}class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget.of(context)!.data.toString());
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }}

接下来创建一个路由页面,包含一个按钮,点击时实现shareddatewidget的值自增:

class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => _InheritedWidgetTestRouteState();}class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: _TestWidget(),//子widget中依赖ShareDataWidget
            ),
            RaisedButton(
              child: Text("Increment"),
              //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新  
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }}

    运行后可以发现,当子组件_TestWidget中依赖父组件ShareDataWidget的数据data发生变化时,子组件的didChangeDependencies回调会被调用,关键代码在于return Text(ShareDataWidget.of(context)!.data.toString());

    一般来说作为子widget很少需要重写didChangeDependencies,因为在依赖发生改变后flutter框架自身也会调用build重新构建widget树,在此之外,如果我们需要执行一些消耗资源较多的操作,如网络请求时,可以在此回调中执行,以避免每次build都要执行一次资源开销;


关于InheritedWidget


    

        前面我们有发现子组件建立与父组件的依赖关系是由于代码行Text(ShareDataWidget.of(context)!.data.toString()),那么有没有办法在子组件依赖的父组件数据发生变化时,子组件不执行didChangeDependencies回调呢?对于这类问题,我们可以考虑从获取父组件对象的方式替换考虑,将实例中父组件ShareDataWidget为其子组件提供获取数据的方法接口由

context.dependOnInheritedWidgetOfExactType<ShareDataWidget>()

调整为context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget即可,那么二者的区别在于何处?让我们来看一下具体实现:

@overrideInheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;}

@overrideInheritedWidget dependOnInheritedWidgetOfExactType({ 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;}

可以看到后者多加了dependOnInheritedElement处理:

@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}

可见其中进行了依赖关系绑定,所以在调用dependOnInheritedWidgetOfExactType时,InheritedWidget
和依赖它的子孙组件关系便完成了注册。


关于缓存的使用


    

    上述由ShareDataWidget.of()改为getElementForInheritedWidgetOfExactType()时,点击子组件的increment按钮会发_TestWidgetState的didChangeDependencies()方法不会再被调,但是它的build()仍在调起,原因是由于点击increment后,调用其本身_InheritedWidgetTestRouteState的setstate重构整个页面,由于_testwidget没有缓存,所以也会被重构建调起build(),那么这个问题场景里有没有办法能达到只更新重构子树中与shareddatawidget建立依赖关系的组件,而不是全部widget子节点重构?这是可以考虑使用封装一个statefulwidget将子widget树缓存起来作为缓存。


    对于widget的缓存,不得不提及provider,因为篇幅关系,让我们把关于provider的分享留下下一期进行,今天的分享就到这里,感谢各位看官支持,如果有任何疑问或者新的想法,欢迎随时私信雷记讨论,让我们下回继续分解!


文章转载自燕山雷震子,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论