Flutter中InheritedWidget的原理

1,774 阅读4分钟

Flutter中InheritedWidget的原理

Flutter的响应式开发与React类似,数据都是自顶向下的。

假设有祖先组点A,中间经过结点B, C,然后到结点D,D需要从A中获取数据f,那按照自顶向下数据流转,f需要依次传递给B及C,最后才到C。这样开发极为不灵活,成本也比较高。所有Flutter需要有跨结点(只能是祖先后代节点,不能跨兄弟节点)高效传递数据的方案。

InheritedWidget

前面讲过InheritedWidget,它是Flutter用来向后代结点高效传递数据用的。

当调用BuildContext.inheritFromWidgetOfExactType时会建立两个结点的依赖关系,每次当祖先组点数据改变后,会主动通知到后代结点,导致后代结点的重建(如果后代结点是StatefulWidget,还是调用State.didChangeDependencies)。

那这种依赖关系是怎么建立起来的?

每个Element维护了所有祖先遗传结点(遗传结点即InheritedWidget对应的InheritedElement,后面会沿用)的表_inheritedWidgets_inheritedWidgets该表记录了该结点至根结点所有的遗传节点。

每个Element维护了自己依赖的遗传结点的表_dependencies

_inheritedWidgets记录所有的祖先遗传结点列表,而_dependencies只记录自己依赖的祖先遗传结点列表。

InheritedElement则维护了依赖自己的后代节点列表_dependents

当调用BuildContext.inheritFromWidgetOfExactType方法时,如果找到相应类型的最近的遗传节点,则将该遗传结点添加到自己的依赖表中(_dependencies),同时会调用祖先遗传结点的InheritedElement.updateDependencies方法,将自己注册到InheritedElement_dependents表中。

此时BuildContext.inheritFromWidgetOfExactType已经完成依赖关系的构建,后面描述InheritedElementInheirtedWidget发生变动后,InheritedElement会遍历_dependents表并调用Element.didChangeDependencies

这节我们从源码入手,搞清楚这种依赖关系建立起来的原理。

Element怎么维护所有祖先遗传结点表_inheritedWidgets

abstract class Element extends DiagnosticableTree implements BuildContext {

  Map<Type, InheritedElement> _inheritedWidgets;

  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    ...
    _updateInheritance();
    ...
  }

  @mustCallSuper
  void activate() {
    ...
    _updateInheritance();
  }

  @mustCallSuper
  void deactivate() {
    ...
    _inheritedWidgets = null;
    ...
  }

  void _updateInheritance() {
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
}

class InheritedElement extends ProxyElement {
  @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;
  }
}

在Element树第首次构建的过程中,都会调用Element.mountElement.mount又会调用Element._updateInheritance,如果该结点是InheritedElement类型,则将自己添加到_inheritedWidgets表中,否则,_inheritedWidgets引用父结点的_inheritedWidgets。随着Element的递归构建,每个Element._inheritedWidgets也被正确填充。

随后,当该Element被从树是移除时,会调用Element.deactivate,会将_inheritedWidgets置空。在动画帧结束之前,如果该Element被重新整个进树中(因为GlobalKey),会重新调用Element.activate,进而重新填充_inheritedWidgets

Element怎么维护自己依赖的所有祖先遗传结点表_dependencies

abstract class Element extends DiagnosticableTree implements BuildContext {

  Set<InheritedElement> _dependencies;


  @mustCallSuper
  void activate() {
    ...
    _dependencies?.clear();
    ...
  }

  @mustCallSuper
  void deactivate() {
    ...
    if (_dependencies != null && _dependencies.isNotEmpty) {
      for (InheritedElement dependency in _dependencies)
        dependency._dependents.remove(this);
    }
    _inheritedWidgets = null;
    ...
  }


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

  @override
  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;
  }
}

当调用BuildContext.inheritFromWidgetOfExactType时,如果在所有祖选遗传表中找到相应遗传结点,就将该遗传结点添加到自己的依赖表中_dependencies,同时将自己注册到相应遗传结点(调用InheritedElement.updateDependencies注册到InheritedElement.dependents表中)。

当Element从树中移除是,会调用Element.deactivate,会遍历所有自己注册的依赖祖先遗传结点,从遗传结点中移除自己,这样,与遗传结点的依赖便取消了,而后将_inheritedWidgets置空。

当Element重新整合进树中时,会再次清理一下_inheritedWidgets(以防从树中移除后,仍然调用BuildContext.inheritFromWidgetOfExactType)。

InheritedElement的更新机制

class InheritedElement extends ProxyElement {

  final Map<Element, Object> _dependents = HashMap<Element, Object>();

  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }

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

  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);///super.updated会调用notifyClients
  }

  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

  @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);
    }
  }

}

当InheritedElement需要更新时(相同位置再次配置了相同runtimeType及key的InheritedWidget)时,会再次调用InheritedWidget.updateShouldNotify确认一下是否需要通知,如果确实需要通知,则会遍历_denpendents并调用Element.didChangeDependencies