【Flutter核心类分析】深入理解数据共享InheritedWidget

背景

Flutter提供了用于Widget间共享数据的InheritedWidget,当InheritedWidget发生变化时,它的子树中所有依赖了它的数据的Widget都会进行rebuild,这使得开发者省去了维护数据同步逻辑的麻烦。InheritedWidget看名字就是一个Widget,那么它是如何做到数据共享,如何做到通知更新的呢?下面我们一起来看看。

InheritedWidget用法

InheritedWidget用法分为以下几个步骤;

  • 自定义Widget继承自InheritedWidget,并且自定义static of方法,用于child获取当前实例
  • 实现InheritedWidtet类中的updateShouldNotify方法,用于返回update条件;
  • 当数据变化时,调用自定义widget的State类中的setState()方法,触发整棵InheritedWIdget tree的更新。

代码就不贴了,相当简单。下面我们来一步步分析它的原理。

原理

InheritedWidget源码

先来看看InheritedWidget的源码部分

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key? key, required Widget child })
    : super(key: key, child: child);

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

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

他继承自ProxyWidget,我们再来看看proxyWidget

abstract class ProxyWidget extends Widget {
  const ProxyWidget({ Key? key, required this.child }) : super(key: key);
  final Widget child;
}

通过上面的总共不超过10行代码的源码,我们看出来InheritedWidget内部除了实现createElement()创建InheritedElement之外就没有其他的操作,我们继续看InheritedElement

InheritedElement源码

class InheritedElement extends ProxyElement {
  InheritedElement(InheritedWidget widget) : super(widget);

  @override
  InheritedWidget get 这个Set记录了所有依赖的Element => super.widget as InheritedWidget;

  //这个Set记录了所有依赖的Element,这里的value值一般都是null
  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

  // 该方法会在Element mount和activate方法中调用
  // _inheritedWidgets为基类Element中的成员,缓存父节点中所有相关的InheritedElement
  @override
  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.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;
  }

  //该方法在父类ProxyElement的update(ProxyWidget oldWidget)方法中调用,看名字就知道是通知依赖方该进行更新了,这里首先会调用重写的updateShouldNotify方法是否需要进行更新,然后遍历_dependents列表并调用didChangeDependencies方法,该方法内会调用mardNeedsBuild,于是在下一帧绘制流程中,对应的Widget就会进行rebuild,界面也就进行了更新
  @override
  void notifyClients(InheritedWidget oldWidget) {
    for (final Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
  }
  
  // 更新所有的子项
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
}

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

我们看到InheritedElement同样继承自ProxyElement。核心方法也就三个,根据注释理解

下面我们继续看,既然Element在update的时候,会调用到notifyClients方法,从而遍历更新所有的_dependens,那么_InheritedElement的dependents是在什么时候被添加进去的呢?

代码最后跟进到:Element.dependOnInheritedElement方法

 @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    //在ancestor.updateDependencies方法中更新_dependents[dependent],以this作为key
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      // 这里调用了dependOnInheritedElement方法
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

相信大家如果看过深入理解BuildContext一文或者实际使用过InheritedWidget的朋友对这两个方法肯定不陌生。对于想读取上级Widget共享的数据来说,通俗的约定就是在上级的Widget类,定义static的of方法,该方法中通过dependOnInheritedWidgetOfExactType获取parent中最近的InheritedWidget的实例并返回,并且同时将自己注册到_dependents中,这样当parent的InheritedWidget更新的时候,就会间接通过notifyCLients方法来更新自己了。

总结

  • InheritedElement的父节点们是无法查找到自己的,即InheritedWidget的数据只能由父节点向子节点传递,反之不能。
  • 如果某节点的父节点有不止一个同一类型的InheritedWidget,调用dependOnInheritedWidgetOfExactType获取到的是离自身最近的该类型的InheritedWidget。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>