Flutter小知識--InheritedWidget之庖丁解牛

ershixiong發表於2019-07-16

前面我們講了InheritedWidget的用途和用法,還留了一個小問題。前文連結

Flutter framework是怎麼知道子widget有沒有依賴InheritedWidget的?

InheritedWidget 定義

首先看一下 InheritedWidget 的定義:

abstract class InheritedWidget extends ProxyWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  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.
  ///
  /// When this widget is rebuilt, sometimes we need to rebuild the widgets that
  /// inherit from this widget but sometimes we do not. For example, if the data
  /// held by this widget is the same as the data held by `oldWidget`, then we
  /// do not need to rebuild the widgets that inherited the data held by
  /// `oldWidget`.
  ///
  /// The framework distinguishes these cases by calling this function with the
  /// widget that previously occupied this location in the tree as an argument.
  /// The given widget is guaranteed to have the same [runtimeType] as this
  /// object.
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製程式碼

繼承自 ProxyWidget, 包含createElement()updateShouldNotify 兩個方法。updateShouldNotify的註釋較為完整, 定義了是否需要通知子樹,那麼該如何通知呢?我們看一下createElement()返回的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;
  }

  ....
}  
複製程式碼

其中 _parent?._inheritedWidgets 又是什麼? 繼續跟蹤發現, 這是Element擁有的屬性:

Map<Type, InheritedElement> _inheritedWidgets;
複製程式碼

其中儲存了祖先節點中出現的 InheritedWidget 與其對應 element 的對映關係。在 element 的 mount 階段active 階段,會執行 _updateInheritance() 方法更新這個對映關係。

對於普通 Element 例項,_updateInheritance() 只是單純把父 element 的 _inheritedWidgets 屬性儲存在自身 _inheritedWidgets 裡。從而實現對映關係的層層向下傳遞。

@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;
}
複製程式碼

此時我們再回過頭來看,InheritedElement的 _updateInheritance的不同,InheritedElement 例項會把自身的資訊新增到 _inheritedWidgets 屬性中,這樣其子孫 element 就可以通過前面提到的 _inheritedWidgets 的傳遞機制獲取到此 InheritedElement 的引用。

鋪墊的差不多,可以看InheritedWidget如何進行更新通知了。

InheritedWidget 更新通知機制

前文提到,想要獲取"最近"的InheritedElement,需要呼叫 BuildContext.inheritFromWidgetOfExactType。那跟Element有什麼關係呢?
其實,Element就是BuildContext。原始碼定義如下:

abstract class Element extends DiagnosticableTree implements BuildContext 
複製程式碼

接下來看BuildContext.inheritFromWidgetOfExactType的原始碼:

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

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

首先在 _inheritedWidget 對映中查詢是否有特定型別 InheritedWidget 的例項。如果有則將該例項新增到自身的依賴列表中,同時將自身新增到對應的依賴項列表中。這樣該 InheritedWidget 在更新後就可以通過其 _dependents 屬性知道需要通知哪些依賴了它的 widget。

每當 InheritedElement 例項更新時,會呼叫 InheritedWidget中的updated方法:

/// Calls [Element.didChangeDependencies] of all dependent elements, if
/// [InheritedWidget.updateShouldNotify] returns true.
///
/// Called by [update], immediately prior to [build].
///
/// Calls [notifyClients] to actually trigger the notifications.
@override
void updated(InheritedWidget oldWidget) {
  if (widget.updateShouldNotify(oldWidget))
    super.updated(oldWidget);
}
複製程式碼

上面提到繼承自 ProxyWidget, 我們來看 ProxyWidget的 updated 方法:

@protected
void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}
複製程式碼

又回到了 InheritedWidget 中notifyClients :

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

@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}
複製程式碼

終於看到熟悉的didChangeDependencies了,哈哈,終於可以解釋 前文中 如果_TestWidget的build方法中沒有使用ShareDataWidget的資料,那麼它的didChangeDependencies()將不會被呼叫,因為它並沒有依賴ShareDataWidget。

總結一下,首先執行相應 InheritedWidget 上的 updateShouldNotify 方法判斷是否需要通知,如果該方法返回 true 則遍歷 _dependents 列表中的 element 並執行他們的 didChangeDependencies() 方法。這樣 InheritedWidget 中的更新就通知到依賴它的子 widget 中了。

原始碼分析結束了,後續可能會再講一些 InheritedWidget的具體應用。


如果你覺得這篇文章對你有益,還請幫忙轉發和點贊,萬分感謝。

Flutter爛筆頭

相關文章