Flutter中InheritedWidget的原理

阿輝_發表於2019-08-16

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

相關文章