InheritedWidget原理

171 阅读3分钟
class FrogColor extends InheritedWidget {
  const FrogColor({Key key, @required Widget child, @required this.color})
    : assert(child != null),
  super(key: key, child: child);
  
  final Color color;
  
  static FrogColor of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<FrogColor>();
  }
  
  @override
  bool updateShouldNotify(FrogColor old) {
    return old.color != color;
  }
}

我们带着两个疑问来总结一下知识

  • 为什么获取InheritedWidget的context必须是它的子节点
  • 为什么InheritedWidget所有的子节点都可以访问它

前置知识

Element基类中的字段

  • _inheritedWidgets:存储了当前树上所有的InheritedElement,
    • _inheritedWidget是一个Map<Type, InheritedElement>,它管理了该树上所有的InheritedWidget对应的Element, 对应的Key是widget的runtimeType
    • 该Map是从根节点沿着子节点往下传递的,传递时机是Element对象mount时
  • _dependencies:存储了当前Element依赖了哪些InheritedElement
    • _dependencies类型是Set
    • 在触发dependOnInheritedWidgetOfExactType方法时才说明当前节点对该InheritedElement有依赖,才需要加入_dependencies中
  Map<Type, InheritedElement>? _inheritedWidgets; 
  Set<InheritedElement>? _dependencies;

InheritedElement中的字段

  • _dependents:存储了依赖该InheritedElement的Element
    • _dependents是一个Map<Element, Object?>,添加到_dependents的方法调用顺序是dependOnInheritedWidgetOfExactType-> InheritedElement.updateDependencie -> setDependencies
    • _dependents有为什么用呢?在InheritedElement更新时会调用notifyClients方法(这是Element的知识,可以查看这里),在notifyClients里面遍历_dependents的元素执行didChangeDependencies()方法进行rebuild,如果是StatefulElement,还会触发State.didChangeDependencies,方法执行顺序是:ProxyElement.update-> InheritedElement.updated-> ProxyElement.updated-> InheritedElement.notifyClients-> (遍历执行)notifyDependent -> Element.didChangeDependencies
 final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

 void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
 }

  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
  }
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

_inheritedWidgets传递给子节点

既然子节点都可以访问InheritedWidget,那么就需要子节点能存储inheritedWidget的信息,前面说了,_inheritedWidgets是个map,是从根节点沿着树往下传递的。

  • 在Element基类中, Element对象被mount(挂载)时,调用了_updateInheritance()方法
void mount(Element? parent, dynamic newSlot) {
  _parent = parent;
  _slot = newSlot;
  _lifecycleState = _ElementLifecycle.active;
  _depth = _parent != null ? _parent!.depth + 1 : 1;
  if (parent != null) // Only assign ownership if the parent is non-null
    _owner = parent.owner;
  final Key? key = widget.key;
  if (key is GlobalKey) {
    key._register(this);
  }
  _updateInheritance();
  }
  • 当该Element是非InheritedElement时,那么该方法只获取了parent的_inheritedWidget
  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
  • 而如果当前Element是InheritedElement, 在获取parent的_inheritedWidget同时,他也会把自己加入到_inheritedWidget中.
  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; //将自己添加进去
  }
  • _inheritedWidget会沿着子树一层层的传递,如果是InheritedElement, 还会把自己加入,这也是解释了child为什么都可以通过自身的context获取InheritedWidget的值;也解释了为什么传入父节点到InheritedWidget中会报错,因为是沿着子树传递的,父节点是没法访问InheritedWidget

从子节点中获取inheritedWidget

我们知道,Element实现了BuildContext,所以context就是访问element,通过静态函数of调用context.context.dependOnInheritedWidgetOfExactType<FrogColor>()_inheritedWidgets中获取对应的InheritedWidget,并且返回

static FrogColor of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<FrogColor>();
 }
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    //我们知道,_inheritedWidgets存储的时候使用widget的runtimeType当作key
    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;
  }

  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    _dependencies ??= HashSet<InheritedElement>();
    //将InheritedElement添加进_dependencies,前置知识说了它的作用
    _dependencies!.add(ancestor);
    //这个方法是将当前Element添加进InheritedElement管理的_dependents中,前面也说了
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget; //返回InheritedWidget
  }

最后回答上面的问题

因为InheritedElement的传递方向是从上往下的,所以只有子节点能访问