数据共享:InheritedWidget

数据共享:InheritedWidget

解释:
Flutter中InheritedWidget可以实现不同子Widget(可以是不同的页面)中共享数据,依赖了同一个InheritedWidget的Widget会在InheritedWidget中数据改变的时候同步数据。

原理:
InheritedWidget中可以存放数据data,它的子组件使用了InheritedWidget中的data,那么这个子组件就依赖于该InheritedWidget,所以子组件是否依赖InheritedWidget主要在于子组件是否使用了父组件InheritedWidget中的数据。形成依赖后,当InheritedWidget中的数据data改变时,就会通知到依赖它的子组件。我们知道在StatefulWidget的State对象中有一个didChangeDependencies回调,看名字就知道,它会在依赖的组件发生变化时被调用。另外我们知道 initState -> didChangeDependencies -> build,所以这个时候就会调用build函数进行页面重构。当然,如果子组件没有使用到父组件InheritedWidget中的数据时,也就没有产生依赖关系,InheritedWidget中的data变化时也就不会通知该子组件。

这里我们先定义一个InheritedWidget:

class DataWidget extends InheritedWidget{
   //定义数据
    int data;
    //提供子组件获取数据的方法,传入该InheritedWidget关联的BuildContext对象
    static DataWidget of(BuildContext context){
        //从名字就知道,从BuildContext树上取出指定类型的InheritedWidget,并建立依赖关系
        //return context.inheritFromWidgetOfExactType(DataWidget);//这个方法在新版本已经过时了使用了dependOnInheritedWidgetOfExactType函数代替,所以这里可以这样写
        return context.dependOnInheritedWidgetOfExactType<DataWidget>();
    }
    
    @override
    bool updateShouldNotify(DataWidget old){
        return old.data != data;
    }
    
}

疑问:通过上边的描述可知,当我们InheritedWidget的子组件调用of函数获取到data的时候,子组件与InheritedWidget就产生了依赖关系,那假如我只想主动的通过of函数获取父组件InheritedWidget中的data,而并不想让InheritedWidget在数据发生改变时通知我的子组件,简单点说就是我的子组件只想主动去拿你的数据,但又不想跟你建立依赖关系,我要怎么做呢?

最简单的就是当子组件通过of取数据时,不建立依赖关系即可。没有了依赖关系,当父组件InheritedWidget中的数据发生变化时,该子组件的didChangeDependencies -> build系列函数自然就不会被调用了,也就不会重构了。

InheritedWidget作为遗传学专家,它当然知道我们的一些特殊需求啦!从上边代码中可以看出子组件获取数据是通过BuildContext的inheritFromWidgetOfExactType函数返回指定的数据类型,我们来看看inheritFromWidgetOfExactType函数的注释说明:

/// Obtains the nearest widget of the given type, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.

/// This method is deprecated. Please use [dependOnInheritedWidgetOfExactType] instead.
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
@Deprecated(
  'Use dependOnInheritedWidgetOfExactType instead. '
  'This feature was deprecated after v1.12.1.'
)
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect });

上边这个方法在1.12.1版本中已经过时了,使用了dependOnInheritedWidgetOfExactType方法代替:

/// Obtains the nearest widget of the given type [T], which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
///
/// This is typically called implicitly from `of()` static methods, e.g.
/// [Theme.of].
///
/// This method should not be called from widget constructors or from
/// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this
/// (directly or indirectly) from build methods, layout and paint callbacks, or
/// from [State.didChangeDependencies].
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor in [State.didChangeDependencies].
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
/// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
/// is not going to be cached and reused later.
///
/// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often.
///
/// Once a widget registers a dependency on a particular type by calling this
/// method, it will be rebuilt, and [State.didChangeDependencies] will be
/// called, whenever changes occur relating to that widget until the next time
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
///
/// The [aspect] parameter is only used when [T] is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect });

简单翻译一下:从BuildContext的Widget树上获取最近指定的类型的Widget,指定的Widget必须是InheritedWidget的子类,并且指定的Widgt挂载到了该BuildContext上,这个Widget改变时,该BuildContext会rebuilt,这样获取数据的子组件就会重新build,所以它就可以从该InheritedWidget中获取到最新的数据。

通俗易懂点:我们的InheritedWidget是挂载到BuildContext,它的子组件自然也在该BuildContext树上,当InheritedWidget发生变化时,BuildContext会rebuilt,从而子组件就就能重新build获取最新的数据。

到此我们知道了,子组件与父组件InheritedWidget是如何建立依赖关系的,并且知道子组件是怎么取数据的,InheritedWidget数据变化时,子组件是怎么获取最新数据的。

现在我们来解决上边那个疑问,BuildContext提供了另一个函数

/// Obtains the element corresponding to the nearest widget of the given type,
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// This method is deprecated. Please use [getElementForInheritedWidgetOfExactType] instead.
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
@Deprecated(
'Use getElementForInheritedWidgetOfExactType instead. ’
‘This feature was deprecated after v1.12.1.’
)
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType);

上边可以看出这个函数已经被抛弃了,新版使用getElementForInheritedWidgetOfExactType,所以我们来看看getElementForInheritedWidgetOfExactType:

/// Obtains the element corresponding to the nearest widget of the given type [T],
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// Calling this method is O(1) with a small constant factor.
///
/// This method does not establish a relationship with the target in the way
/// that [dependOnInheritedWidgetOfExactType] does.
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor by calling
/// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
/// safe to use this method from [State.deactivate], which is called whenever
/// the widget is removed from the tree.
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();

从上边红色注释可以看出,使用该方法获取InheritedWidget时不会建立依赖关系。
所以我们重写of函数:

class DataWidget extends InheritedWidget{
   //定义数据
    int data;
    //提供子组件获取数据的方法,传入该InheritedWidget关联的BuildContext对象
    static DataWidget of(BuildContext context){
        //从名字就知道,从BuildContext树上取出指定类型的InheritedWidget,但不建立依赖关系
        //return context.ancestorInheritedElementForWidgetOfExactType(DataWidget).widget;//这个方法在新版本已经过时了使用了getElementForInheritedWidgetOfExactType函数代替,所以这里可以这样写
        return context.getElementForInheritedWidgetOfExactType(DataWidget).widget;
    }
    
    @override
    bool updateShouldNotify(DataWidget old){
        return old.data != data;
    }
    
}

通过以上改动我们就可以实现子组件通过InheritedWidget的of函数获取数据的时候不建立依赖关系的能力。

接下来简单看下几个方法:

/// Obtains the nearest widget of the given type [T], which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
///
/// This is typically called implicitly from `of()` static methods, e.g.
/// [Theme.of].
///
/// This method should not be called from widget constructors or from
/// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this
/// (directly or indirectly) from build methods, layout and paint callbacks, or
/// from [State.didChangeDependencies].
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor in [State.didChangeDependencies].
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
/// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
/// is not going to be cached and reused later.
///
/// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often.
///
/// Once a widget registers a dependency on a particular type by calling this
/// method, it will be rebuilt, and [State.didChangeDependencies] will be
/// called, whenever changes occur relating to that widget until the next time
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
///
/// The [aspect] parameter is only used when [T] is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {//T的类型是final type = _typeOf<InheritedProvider<T>>();
  assert(_debugCheckStateIsActiveForAncestorLookup());
//Map<Type, InheritedElement> _inheritedWidgets;
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];//_inheritedWidgets是在mount的时候将InheritedWidget放进去
  if (ancestor != null) {
    assert(ancestor is InheritedElement);
    return dependOnInheritedElement(ancestor, aspect: aspect);//建立依赖关系
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}


@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
  assert(ancestor != null);
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies.add(ancestor);//将子组件挂载到到InheritedWidget上,建立依赖关系
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget;
}

  /// Notifies all dependent elements that this inherited widget has changed, by
  /// calling [Element.didChangeDependencies].
  ///
  /// This method must only be called during the build phase. Usually this
  /// method is called automatically when an inherited widget is rebuilt, e.g.
  /// as a result of calling [State.setState] above the inherited widget.
  ///
  /// See also:
  ///
  ///  * [InheritedNotifier], a subclass of [InheritedWidget] that also calls
  ///    this method when its [Listenable] sends a notification.
  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies.contains(this));
      notifyDependent(oldWidget, dependent);//当InheritedWidget发生变化时,通知所有依赖于它的子组件
    }
  }
}

/// Called by [notifyClients] for each dependent.
///
/// Calls `dependent.didChangeDependencies()` by default.
///
/// Subclasses can override this method to selectively call
/// [didChangeDependencies] based on the value of [getDependencies].
///
/// See also:
///
///  * [updateDependencies], which is called each time a dependency is
///    created with [dependOnInheritedWidgetOfExactType].
///  * [getDependencies], which returns the current value for a dependent
///    element.
///  * [setDependencies], which sets the value for a dependent element.
///  * [InheritedModel], which is an example of a class that uses this method
///    to manage dependency values.
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();//调用依赖于InheritedWidget的子组件的
}

/// Obtains the element corresponding to the nearest widget of the given type [T],
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// Calling this method is O(1) with a small constant factor.
///
/// This method does not establish a relationship with the target in the way
/// that [dependOnInheritedWidgetOfExactType] does.
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor by calling
/// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
/// safe to use this method from [State.deactivate], which is called whenever
/// the widget is removed from the tree.
@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;//并没有建立依赖关系
}

ok,到此我们清楚的了解了依赖关系的建立,以及当InheritedWidget的数据发生变化时,为什么只有建立了依赖关系的子组件会重新执行build。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr大伟哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值