Flutter狀態管理-02-InheritedWidget資料共享的原理分析

天色將變發表於2021-03-02

InheritedWidget的基礎使用,參考:www.jianshu.com/p/290d9c60f…

是什麼

在InheritedWidget的文件註釋中是這麼描述的:

Base class for widgets that efficiently propagate information down the tree.

在渲染樹中有效向子樹傳遞資訊的基類。

從app的入口:

void main() {
  runApp(MyApp());
  // runApp(CustomInheritedWidget());
}
複製程式碼

就開始構建一顆渲染樹,MyApp()可看作該樹的根節點,如果將該根節點的Widget設為一個InheritedWidget,那麼其子樹中所有的子樹子節點都可獲取到該InheritedWidget內共享到的資料。當InheritedWidget中的資料發生改變時,所有依賴該資料的子widget都會重新build。

有什麼用

圍繞 ”共享,共變“的特點

  • 統一主題設定,更改主題後,所有的子頁面都會即時更改。
  • 專案基礎資料,如使用者資訊、許可權等公共資料的共享。
  • 狀態管理
  • ......
核心使用流程
  • 自定義CustomInheritedWidget 繼承 InheritedWidget,將需要共享的資料設為成員變數。重寫updateShouldNotify方法,在該方法內明確當什麼情況下會讓那些依賴此共享資料的widget重新build。
  • 在需要依賴此資料的widget中獲取該共享資料,CustomInheritedWidget.of(context).data.toString(),data為自定義的共享資料,可以是其他。重寫didChangeDependencies方法,當該共享資料發生變化時,觸發此方法。
  • 將依賴者成為CustomInheritedWidget的一個子widget
原始碼分析
  • InheritedWidget,引數為一個child,將依賴者設為為child或此child的子孫。Widget的核心作用是為element配置資料,這裡的InheritedElement才是真正發揮作用的類。updateShouldNotify方法也是重寫的InheritedElement中的方法,作用是是否通知那些依賴者進行更新。
abstract class InheritedWidget extends ProxyWidget {
  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.
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製程式碼
  • 進入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;
  }

  @override
  void debugDeactivated() {
    assert(() {
      assert(_dependents.isEmpty);
      return true;
    }());
    super.debugDeactivated();
  }

  /// Returns the dependencies value recorded for [dependent]
  
  @protected
  Object getDependencies(Element dependent) {
    return _dependents[dependent];
  }

  /// Sets the value returned by [getDependencies] value for [dependent].
  
  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

  /// Called by [dependOnInheritedWidgetOfExactType] when a new [dependent] is added.
  
  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }

  /// Called by [notifyClients] for each dependent.
  
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

  /// Calls [Element.didChangeDependencies] of all dependent elements, if
  /// [InheritedWidget.updateShouldNotify] returns true.
  
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

  /// Notifies all dependent elements that this inherited widget has changed, by
  /// calling [Element.didChangeDependencies].
 
  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final 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);
    }
  }
}
複製程式碼

_dependents: 維護一個map,存放所有依賴者的引用。

**_inheritedWidgets:**這是祖先類Element的一個屬性,當是InheritedElement才會起作用,儲存的是祖先節點中所有出現的InheritedWidget與InheritedElement的對應關係_inheritedWidgets[widget.runtimeType] = this,這裡的widget.runtimeType是Object中的屬性,唯一代表此類,這裡的this是InheritedElement。

**_updateInheritance():**該方法也是Element的方法,在Element的mount()內會呼叫,目的就是更新_inheritedWidgets。需要注意,該方法在Element中有預設實現,是這樣的:

void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
複製程式碼

也就是說判斷父節點中是否有_inheritedWidgets,有的話,賦值給當前element的_inheritedWidgets,如此這樣,每一層的element都會保留上一層的_inheritedWidgets,這也就是為什麼InheritedWidget能一直向下傳遞資料的原因。 而InheritedElement重寫了該方法。主要是這段程式碼:_inheritedWidgets[widget.runtimeType] = this,更新對映。將當前InheritedWidget頁加入到該_inheritedWidgets 中。

**getDependencies():**獲取所有依賴者 **setDependencies():**新增新的依賴者。一般在自定義InheritedWidget時,都會定義一個靜態方法of,用於獲取該自定義的InheritedWidget,如:

static MyInheritedWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(MyInheritedWidget);
  }
複製程式碼

深入context.inheritFromWidgetOfExactType方法內部:

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

繼續深入inheritFromElement():

@override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    return dependOnInheritedElement(ancestor, aspect: aspect);
  }

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

最終定格在:setDependencies(dependent, null); 由此可知,當依賴者使用of獲取自定義InheritedWidget時,就將自己新增進了依賴者集合當中。 **updateDependencies():**更新指定依賴者 notifyDependent():,呼叫依賴者的didChangeDependencies方法,告知依賴的資料發生了變化,這個方法會被notifyClients()方法批量呼叫。 notifyClients():,批量告知依賴者,資料發生了變化。該方法是在ProxyElemnt中被定義,在InheritedElement中被重寫了。當自定義的InheritedWidget內資料發生變化時,會通過重寫的方法updateShouldNotify去定義是否需要通知依賴者更新,updateShouldNotify如:

@override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    // 新舊資料不一致時,返回true,通知依賴本widget的子widget,此時子widget中的didChangeDependencies方法會被呼叫
    return oldWidget.data != data;
  }
複製程式碼

當新舊data不同時要進行通知,這裡的return可以根據實際情況定義。 那什麼時刻呼叫的updateShouldnotify方法呢?在InheritedElement中的updated方法內:

@override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }
複製程式碼

updated()方法是在ProxyElement中被定義的,這裡呼叫super.notifyClients()方法,在ProxyElement中是這樣實現的:

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

notifyClients()在ProxyELement中是空實現,在InheritedElement進行重寫。 如此就將整個流程串聯起來了。

InheritedWidget內部流程
  • 自定義InheritedWidget,設定共享資料,重寫updateShouldNotify方法,提供of靜態方法。
  • 將依賴者成為InheritedWidget的子孫,依賴者呼叫of方法獲取共享資料時,內部就將該依賴者新增依賴者名單_dependents中了。
  • element樹在構建過程中,mount時會通過_updateInheritance方法將_inheritedWidgets層層下傳,在下傳過程中,非InheritedWidget型別的widget會將parent的_inheritedWidgets直接賦給自己,而InheritedWidget型別的widget會將parent的_inheritedWidgets賦給自己,並將自己新增進去。
  • 共享資料發生變化,updated方法判斷updateShouldNotify是否為true,為true就呼叫notifyClients方法,notifyClients內部在呼叫dependent.didChangeDependencies();最終呼叫到依賴者的didChangeDependencies方法內。

相關文章