数据共享组件InheritedWidget

447 阅读5分钟

InheritedWidget

InheritedWidget是Flutter提供的用来从上到下传递数据的功能性组件。平常开发过程当中, 对于嵌套组件、跳转路由页面来说, 从父组件向子组件传递数据, 一般我们首先想到的是通过参数传递, 比较简单。

参数传值

class ParentWidget extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        var value = "this is the value";
        return ChildWidget(value: value);
    }
}

class ChildWidget extends StatelessWidget {
    String value;
    ChildWidget({Key key, this.value}): super(key);
    @override
    Widget build(BuildContext context) {
        return Text(value);
    }
}

通过参数传值的方式, 在Widget嵌套不是很多的情况下, 是很方便、简单的, 推荐使用。但是, 如果Widget嵌套层级过多, 数据一层一层一层一层一层向子组件传递, 很容易造成混乱。这个时候, 就考虑使用InheritedWidget组件, 实现跨层级传递数据了。

InheritedWidget传递数据

下面我们一起来看一个添加数据到购物车的例子。

定义基础InheritedWidget组件

class _BaseInheritedWidget<T> extends InheritedWidget {
  _BaseInheritedWidget({
    Key? key,
    required Widget child,
    required this.data,
  }) : super(key: key, child: child);

  T data;

  @override
  bool updateShouldNotify(_BaseInheritedWidget oldWidget) {
    /// 在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
    return true;
  }
}

上面在定义InheritedWidget的时候,重写了updateShouldNotify方法。该方法作用是,当InheritedWidget重新build的时候,是否要通知依赖于该Widget的子Widget也要重新build。一般是在这个方法中比较InheritedWidget所持有的值是否变化,如果变化了,就通知子Widget重新build,以更新状态。

提供者

接下来, 我们来一起实现数据的提供者。

class CustomProviderWidget extends StatefulWidget {
  CustomProviderWidget({
    Key? key,
    required this.child,
  }): super(key: key);

  final Widget child;

  @override
  CustomProviderState createState() => new CustomProviderState();

  static CustomProviderState of(BuildContext context, [bool rebuild = true]){
    return (rebuild ? context.dependOnInheritedWidgetOfExactType<_BaseInheritedWidget>() as _BaseInheritedWidget
        : context.getElementForInheritedWidgetOfExactType<_BaseInheritedWidget>() as _BaseInheritedWidget).data;
  }
}

该类继承StatefulWidget,然后定义了一个of()静态方法供子类方便获取Widget树中的InheritedProvider中保存的共享状态(data),下面我们实现该类对应的CustomProviderState类:

class CustomProviderState extends State<CustomProviderWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _BaseInheritedWidget(
      data: this,
      child: widget.child,
    );
  }
}

消费者

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CustomProviderState state = CustomProviderWidget.of(context, false);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CustomProviderState state = CustomProviderWidget.of(context, true);
    return new Text('I am Widget B.  itemsCount: ${state.itemsCount} \n \n \n \n \n \n');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C');
  }
}

我们定义了两个个消费者WidgetAWidgetB, 它们都使用CustomProviderWidget.of()方法获取父组件的state。

使用

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State<MyTree> {
  @override
  Widget build(BuildContext context) {
    /// 全局共享组件
    return CustomProviderWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: <Widget>[
            new WidgetA(),
            new Container(
              child: new Row(
                children: <Widget>[
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

当我们点击Widget A中的Add Item按钮时, 页面就会出现数据累加。

AddItem.png

至此, 我们完成了一个通过共享组件InheritedWidget层层向下传递数据的案例。

源码分析

Element

abstract class Element extends DiagnosticableTree implements BuildContext {

Map<Type, InheritedElement>? _inheritedWidgets;
    Set<InheritedElement>? _dependencies;
    
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
  assert(_lifecycleState == _ElementLifecycle.initial);
  assert(widget != null);
  assert(_parent == null);
  assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
  assert(slot == null);
  _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. If parent is null
    // (the root node), the owner should have already been assigned.
    // See RootRenderObjectElement.assignOwner().
    _owner = parent.owner;
  }
  assert(owner != null);
  final Key? key = widget.key;
  if (key is GlobalKey) {
    owner!._registerGlobalKey(key, this);
  }
  
  /// 调用_updateInheritance方法
  _updateInheritance();
}


@mustCallSuper
void activate() {
  assert(_lifecycleState == _ElementLifecycle.inactive);
  assert(widget != null);
  assert(owner != null);
  assert(depth != null);
  final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
  _lifecycleState = _ElementLifecycle.active;
  // We unregistered our dependencies in deactivate, but never cleared the list.
  // Since we're going to be reused, let's clear our list now.
  _dependencies?.clear();
  _hadUnsatisfiedDependencies = false;
  /// 调用_updateInheritance方法
  _updateInheritance();
  if (_dirty)
    owner!.scheduleBuildFor(this);
  if (hadDependencies)
    didChangeDependencies();
}

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


@pragma('vm:prefer-inline')
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
    // When the type of a widget is changed between Stateful and Stateless via
    // hot reload, the element tree will end up in a partially invalid state.
    // That is, if the widget was a StatefulWidget and is now a StatelessWidget,
    // then the element tree currently contains a StatefulElement that is incorrectly
    // referencing a StatelessWidget (and likewise with StatelessElement).
    //
    // To avoid crashing due to type errors, we need to gently guide the invalid
    // element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
    // returns false which prevents us from trying to update the existing element
    // incorrectly.
    //
    // For the case where the widget becomes Stateful, we also need to avoid
    // accessing `StatelessElement.widget` as the cast on the getter will
    // cause a type error to be thrown. Here we avoid that by short-circuiting
    // the `Widget.canUpdate` check once `hasSameSuperclass` is false.
    assert(() {
      final int oldElementClass = Element._debugConcreteSubtype(child);
      final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
      hasSameSuperclass = oldElementClass == newWidgetClass;
      return true;
    }());
    if (hasSameSuperclass && child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
        
        /// 调用update方法
      child.update(newWidget);
      assert(child.widget == newWidget);
      assert(() {
        child.owner!._debugElementWasRebuilt(child);
        return true;
      }());
      newChild = child;
    } else {
      deactivateChild(child);
      assert(child._parent == null);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }

  assert(() {
    if (child != null)
      _debugRemoveGlobalKeyReservation(child);
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
      assert(owner != null);
      owner!._debugReserveGlobalKeyFor(this, newChild, key);
    }
    return true;
  }());

  return newChild;
}


 @override
 T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    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;
 }
  
 @override
 InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
     final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
     return ancestor;
 }
}

ComponentElement

ComponentElement 继承 Element, 关键是执行mount后, 会去执行update

abstract class ComponentElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  ComponentElement(Widget widget) : super(widget);

  Element? _child;

  bool _debugDoingBuild = false;
  @override
  bool get debugDoingBuild => _debugDoingBuild;

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_lifecycleState == _ElementLifecycle.active);
    _firstBuild();
    assert(_child != null);
  }

  void _firstBuild() {
    rebuild();
  }

  /// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
  /// (for stateless widgets) or the [State.build] method of the [State] object
  /// (for stateful widgets) and then updates the widget tree.
  ///
  /// Called automatically during [mount] to generate the first build, and by
  /// [rebuild] when the element needs updating.
  @override
  @pragma('vm:notify-debugger-on-exception')
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);

    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
    Widget? built;
    try {
      assert(() {
        _debugDoingBuild = true;
        return true;
      }());
      built = build();
      assert(() {
        _debugDoingBuild = false;
        return true;
      }());
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }

  /// Subclasses should override this function to actually call the appropriate
  /// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
  /// their widget.
  @protected
  Widget build();

  @override
  void visitChildren(ElementVisitor visitor) {
    if (_child != null)
      visitor(_child!);
  }

  @override
  void forgetChild(Element child) {
    assert(child == _child);
    _child = null;
    super.forgetChild(child);
  }
}

ProxyElement

ProxyElement继承ComponentElement

abstract class ProxyElement extends ComponentElement {
  /// Initializes fields for subclasses.
  ProxyElement(ProxyWidget widget) : super(widget);

  @override
  ProxyWidget get widget => super.widget as ProxyWidget;

  @override
  Widget build() => widget.child;

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    assert(widget != null);
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  /// Called during build when the [widget] has changed.
  ///
  /// By default, calls [notifyClients]. Subclasses may override this method to
  /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
  /// widgets are equivalent).
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  /// Notify other objects that the widget associated with this element has
  /// changed.
  ///
  /// Called during [update] (via [updated]) after changing the widget
  /// associated with this element but before rebuilding this element.
  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}

ProxyElement继承ComponentElement

使用泛型T作为参数, 从_inheritedWidgets字典中获取InheritedElement类型的ancestor对象。 如果ancestor对象不为null, 就调用dependOnInheritedElement方法。

继承关系InheritedElement-->ProxyElement-->ComponentElement-->Element

因为InheritedElement重写了_updateInheritance方法, 所以Element中的mount方法去执行_updateInheritance时, 其实是来到了InheritedElement中的_updateInheritance方法。该方法中_inheritedWidgets[widget.runtimeType] = this; 将InheritedElement自己保存到这个Map中。

  • Element中的mount方法回去执行_updateInheritance
  • Element中的activate方法回去执行_updateInheritance

由此, 我们可以知道, 底层Element_inheritedWidgets Map中包含所有类型为InheritedWidget的祖先InheritedElement节点信息。

InheritedElement

class InheritedElement extends ProxyElement {
  /// Creates an element that uses the given widget as its configuration.
  InheritedElement(InheritedWidget widget) : super(widget);

  @override
  InheritedWidget get widget => super.widget as InheritedWidget;

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

/// 重写`Element`中的方法
  @override
  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>();
      /// 保存`InheritedElement`节点
    _inheritedWidgets![widget.runtimeType] = this;
  }

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

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

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

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

  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

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

updateShouldNotify方法

InheritedWidget重写updateShouldNotify方法,这个方法会在InheritedEelement中的updated方法中被调用

@override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

调用updateShouldNotify方法,如果返回true就再调用super.updated(oldWidget)方法,否则,不执行任何操作。
InheritedElement的父类是ProxyElementProxyElementupdated方法会调用notifyClinets(oldWidget)方法

@protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

notifyClinets(oldWidget)方法是ProxyElement定义的抽象方法,在InheritedElement类中有具体的实现如下:

 @override
  void notifyClients(InheritedWidget oldWidget) {
    ...
    for (final Element dependent in _dependents.keys) {
      ...
      notifyDependent(oldWidget, dependent);
    }
  }

会循环遍历_dependents列表,调用notifyDependent(oldWidget, dependent)方法

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

notifyDependent方法会直接调用dependentdidChangeDependencies方法,这个方法定义在Element类中,实现如下

void didChangeDependencies() {
    ...
    markNeedsBuild();
  }

didChangeDependencies方法会调用Element的markNeedsBuild方法,而markNeedsBuild方法会将该element标记为dirty,在下一帧将会重新build。

总结

InheritedWidget是用于向下传递数据的功能性组件。自定义InheritedWidget时, 需要重写updateShouldNotify方法, 当InheritedWidget重新build的时候,是否要通知依赖于该Widget的子Widget也要重新build。

参考资料

跨组件状态共享

Flutter InheritedWidget源码解析

Widget, State, Context 以及 InheritedWidget