Flutter状态管理——InheritedWidget数据共享的原理分析

Posted 一叶飘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter状态管理——InheritedWidget数据共享的原理分析相关的知识,希望对你有一定的参考价值。

InheritedWidget是什么

在InheritedWidget的文档注释中是这么描述的:

Base class for widgets that efficiently propagate information down the tree.

在渲染树中有效向子树传递信息的基类。

从app的入口:

void main() 
  runApp(MyApp());
  // runApp(CustomInheritedWidget());

就开始构建一颗渲染树,MyApp()可看作该树的根节点,如果将该根节点的Widget设为一个InheritedWidget,那么其子树中所有的子树子节点都可获取到该InheritedWidget内共享到的数据。当InheritedWidget中的数据发生改变时,所有依赖该数据的子widget都会重新build。

有什么用

围绕 ”共享,共变“的特点

  • 统一主题设置,更改主题后,所有的子页面都会即时更改。
  • 项目基础数据,如用户信息、权限等公共数据的共享。
  • 状态管理
  • ......

核心使用流程

  • 自定义CustomInheritedWidget 继承 InheritedWidget,将需要共享的数据设为成员变量。重写updateShouldNotify方法,在该方法内明确当什么情况下会让那些依赖此共享数据的widget重新build。
  • 在需要依赖此数据的widget中获取该共享数据,CustomInheritedWidget.of(context).data.toString(),data为自定义的共享数据,可以是其他。重写didChangeDependencies方法,当该共享数据发生变化时,触发此方法。
  • 将依赖者成为CustomInheritedWidget的一个子widget

源码分析

  • InheritedWidget,参数为一个child,将依赖者设为为child或此child的子孙。Widget的核心作用是为element配置数据,这里的InheritedElement才是真正发挥作用的类。updateShouldNotify方法也是重写的InheritedElement中的方法,作用是是否通知那些依赖者进行更新。
abstract class InheritedWidget extends ProxyWidget 
  const InheritedWidget( Key key, Widget child )
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  /// Whether the framework should notify widgets that inherit from this widget.
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
  • 进入InheritedElement
class InheritedElement extends ProxyElement 
  ...
  final Map<Element, Object> _dependents = HashMap<Element, Object>();

  @override
  void _updateInheritance() 
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  

  @override
  void debugDeactivated() 
    assert(() 
      assert(_dependents.isEmpty);
      return true;
    ());
    super.debugDeactivated();
  

  /// Returns the dependencies value recorded for [dependent]
  
  @protected
  Object getDependencies(Element dependent) 
    return _dependents[dependent];
  

  /// Sets the value returned by [getDependencies] value for [dependent].
  
  @protected
  void setDependencies(Element dependent, Object value) 
    _dependents[dependent] = value;
  

  /// Called by [dependOnInheritedWidgetOfExactType] when a new [dependent] is added.
  
  @protected
  void updateDependencies(Element dependent, Object aspect) 
    setDependencies(dependent, null);
  

  /// Called by [notifyClients] for each dependent.
  
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) 
    dependent.didChangeDependencies();
  

  /// Calls [Element.didChangeDependencies] of all dependent elements, if
  /// [InheritedWidget.updateShouldNotify] returns true.
  
  @override
  void updated(InheritedWidget oldWidget) 
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  

  /// Notifies all dependent elements that this inherited widget has changed, by
  /// calling [Element.didChangeDependencies].
 
  @override
  void notifyClients(InheritedWidget oldWidget) 
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final 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);
    
  

_dependents: 维护一个map,存放所有依赖者的引用。

**_inheritedWidgets:**这是祖先类Element的一个属性,当是InheritedElement才会起作用,保存的是祖先节点中所有出现的InheritedWidget与InheritedElement的对应关系_inheritedWidgets[widget.runtimeType] = this,这里的widget.runtimeType是Object中的属性,唯一代表此类,这里的this是InheritedElement。

**_updateInheritance():**该方法也是Element的方法,在Element的mount()内会调用,目的就是更新_inheritedWidgets。需要注意,该方法在Element中有默认实现,是这样的:

void _updateInheritance() 
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  

也就是说判断父节点中是否有_inheritedWidgets,有的话,赋值给当前element的_inheritedWidgets,如此这样,每一层的element都会保留上一层的_inheritedWidgets,这也就是为什么InheritedWidget能一直向下传递数据的原因。 而InheritedElement重写了该方法。主要是这段代码:_inheritedWidgets[widget.runtimeType] = this,更新映射。将当前InheritedWidget页加入到该_inheritedWidgets 中。

**getDependencies():**获取所有依赖者 **setDependencies():**添加新的依赖者。一般在自定义InheritedWidget时,都会定义一个静态方法of,用于获取该自定义的InheritedWidget,如:

static MyInheritedWidget of(BuildContext context) 
    return context.inheritFromWidgetOfExactType(MyInheritedWidget);
  

深入context.inheritFromWidgetOfExactType方法内部:

InheritedWidget inheritFromWidgetOfExactType(Type targetType,  Object aspect ) 
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) 
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    
    _hadUnsatisfiedDependencies = true;
    return null;
  

继续深入inheritFromElement():

@override
  InheritedWidget inheritFromElement(InheritedElement ancestor,  Object aspect ) 
    return dependOnInheritedElement(ancestor, aspect: aspect);
  

  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor,  Object aspect ) 
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  
@protected
  void updateDependencies(Element dependent, Object aspect) 
    setDependencies(dependent, null);
  

最终定格在:setDependencies(dependent, null); 由此可知,当依赖者使用of获取自定义InheritedWidget时,就将自己添加进了依赖者集合当中。 **updateDependencies():**更新指定依赖者 notifyDependent():,调用依赖者的didChangeDependencies方法,告知依赖的数据发生了变化,这个方法会被notifyClients()方法批量调用。 notifyClients():,批量告知依赖者,数据发生了变化。该方法是在ProxyElemnt中被定义,在InheritedElement中被重写了。当自定义的InheritedWidget内数据发生变化时,会通过重写的方法updateShouldNotify去定义是否需要通知依赖者更新,updateShouldNotify如:

@override
  bool updateShouldNotify(MyInheritedWidget oldWidget) 
    // 新旧数据不一致时,返回true,通知依赖本widget的子widget,此时子widget中的didChangeDependencies方法会被调用
    return oldWidget.data != data;
  

当新旧data不同时要进行通知,这里的return可以根据实际情况定义。 那什么时刻调用的updateShouldnotify方法呢?在InheritedElement中的updated方法内:

@override
  void updated(InheritedWidget oldWidget) 
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  

updated()方法是在ProxyElement中被定义的,这里调用super.notifyClients()方法,在ProxyElement中是这样实现的:

 @protected
  void updated(covariant ProxyWidget oldWidget) 
    notifyClients(oldWidget);
  

notifyClients()在ProxyELement中是空实现,在InheritedElement进行重写。 如此就将整个流程串联起来了。

InheritedWidget内部流程

  • 自定义InheritedWidget,设定共享数据,重写updateShouldNotify方法,提供of静态方法。
  • 将依赖者成为InheritedWidget的子孙,依赖者调用of方法获取共享数据时,内部就将该依赖者添加依赖者名单_dependents中了。
  • element树在构建过程中,mount时会通过_updateInheritance方法将_inheritedWidgets层层下传,在下传过程中,非InheritedWidget类型的widget会将parent的_inheritedWidgets直接赋给自己,而InheritedWidget类型的widget会将parent的_inheritedWidgets赋给自己,并将自己添加进去。
  • 共享数据发生变化,updated方法判断updateShouldNotify是否为true,为true就调用notifyClients方法,notifyClients内部在调用dependent.didChangeDependencies();最终调用到依赖者的didChangeDependencies方法内。

以上是关于Flutter状态管理——InheritedWidget数据共享的原理分析的主要内容,如果未能解决你的问题,请参考以下文章

flutter Provide (状态管理篇)

用于 Web 状态管理的 Flutter

Flutter 状态管理- 使用 MobX

Flutter 响应式状态管理框架GetX

Flutter 响应式状态管理框架GetX

Flutter 状态管理 实践记录