解析 InheritedWidget

506 阅读6分钟

概览

翻译:inherited adj 遗传的;继承权的;通过继承得到的

打开源码可以看到对 InheritedWidget的解释

Base class for widgets that efficiently propagate information down the tree
--在UI树上能够有效传递信息的一个基本组件。

我们可以使用这个类来定制一个组件来传递我们需要在整个界面的状态传递。可以在官方的介绍上看到一个案例:

   ###### 创建一个继承InheritedWidget的组件

其中我们会使用一个惯例的写法,创建一个 static 的方法 of 去获得他对应的InheritedWidget。此方法会间接的调用 BuildContext的

dependOnInheritedWidgetOfExactType方法,范型传入我们当前的 FrogColor组件。

///创建一个InheritedWidget的继承类,用于管理 color 信息的传递
class FrogColor extends InheritedWidget {
  const FrogColor({Key key, @required this.color, @required Widget child})
      : assert(color != null),
        assert(child != null),
        super(key: key, child: child);
  final Color color;
  ///一般的写法,官方推荐
  static FrogColor of(BuildContext context) {
    ///这个方法做了什么事情,待会会进行分析解释!
    return context.dependOnInheritedWidgetOfExactType<FrogColor>();
  }
  @override
  bool updateShouldNotify(covariant FrogColor oldWidget) {
    return color != oldWidget.color;
  }
}

当然我们有时候也可能会直接使用一个Wiget去包裹一个InheritedWidget去传递组件的状态,典型的一个就是Theme组件,参考Theme的源码,我们知道他内部包裹了 _InheritedTheme 和 CupertinoTheme 并且 在 他的字组件中传递了 ThemeData 。所以我们能够很方便的全局的去修改我们的主题。

使用这个InheritedWidgt进行信息的传递

这里我们使用了一个Builder来创建需要使用FrogColor配置信息的子组件。其实我们使用的这个context就是一个Element,这个Element属于FrogColor的Element中的子Element。这一点很重要(关于context的知识需要参考下 Builder的源码)。

class MyPage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
       return Scaffold(
           appBar: AppBar(),
           body: FrogColor(
             color: Colors.green,
             child: Builder(builder: (BuildContext context){
               //这里我门打印了一下context 。  Builder(dirty, dependencies: [FrogColor])
               //这里打印的是 Widget的一些信息,其实最终调用的是Element的 toString 方法
               /// @override
///     String toStringShort() {
///     return widget != null ? widget.toStringShort() : '[${objectRuntimeType(this, 'Element')}]';
/// }
               print(context);
               print(context as Element) ///并不会报错
               return Text("This is InnerText" ,style: TextStyle(
                 color: FrogColor.of(context).color
               ));
             }),
           ),
       );
  }
}

为什么我们需要将需要FrogColor信息的组件外面包裹一层Builder,因为 FrogColor.of(context) 这个context 必须是 FrogColor的子 context

(其实是Element),若是使用 MyPage的context , 这个context 位于FrogColor的父级,那么我们需要的效果肯定会达不到的。

为什么会出现这样的效果,我们应该从 我们 惯例定义的 static 方法 中说起。

dependOnInheritedWidgetOfExactType

这个方法我们调用的是 BuildContext.dependOnInheritedWidgetOfExactType , 其中唯一实现这个方法的类就是 Element,我们看 Element

中的这个方法的实现:

  1. 去 _inheritedWidgets 中找输入范型对应的 InheritedElement,对应 FrogColor中的Element。在FrogColorElement创建的时候会将其加入到 _inheritedWidgets 中。
  2. 在当前 Builder对应的Element中的_dependencies加入 找到的 FrogColorElement ,这样两者就建立了相关联系。
  3. ancestor.updateDependencies(this, aspect) 在 FrogColorElement更新依赖关系。
///from  Eelement 
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
 
  //还记得我么给 这个 方法传入了一 FrogColor的范型。这里 _inheritedWidgets 将类型作为Key 查找 对应的 Element;
  /// _inheritedWidgets 这个 变量 会在Element 的 mount 方法 和 active 方法中调用,就是:如果当前的Elemnt属于InheritedElement便将此Element放入 _inheritedWidgets 中,不是的话向上追溯加入。
  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;
}
​
/// from Element  添加依赖关系
 @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    ///在本Element的依赖链中添加,一切的操作都在本Element中操作而非 FrogColor对应的Element中,
    ///这也是为什么我们在前面的打印中看到了 : Builder(dirty, dependencies: [FrogColor])
    _dependencies.add(ancestor);
    ///刷新依赖关系(这个依赖关系刷新的是 FrogColor中的依赖关(这个一会再讲)
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
​

分析到这一步,其实我们便可以使用 InheritedWidget提供的一些属性信息,但是当我们更新数据的时候(比如:FrogColor 动态更新 color属性),若是子组件的Widget属性没有发生变化(参考 setState()原理)的时候,子组件是不会发生变化的。也就是说只能在第一次构建的时候生效,那么我们全局修改主题,全局修改 ForgColor 的属性怎么办。我们需要解析 ancestor.updateDependencies(this, aspect) 内在都干了什么,怎么运行的。

InHeritedWidget 动态更新

经过以上的分析,我们知道,在 添加依赖关系的时候,会分别在自己的 _dependencies 中添加父级,然后在父级中的 _dependencies 添加 调用的Element。为什么要添加这种依赖关系?我们应分析当 Element更新的时候对这种依赖关系操作了什么。

熟悉Element更新原理的情况下(不知道顺着思路走)我们知道:属性更改后会调用update方法。而 InheritedElement 继承在 ProxyElement ,我们来分析 ProxyElement(这个类里面包含了通知客户端更新的定义方法) 中的update 做了什么。

/// from ProxyElement
@override
void update(ProxyWidget newWidget) {
  final ProxyWidget oldWidget = widget;
  assert(widget != null);
  assert(widget != newWidget);
  super.update(newWidget);
  assert(widget == newWidget);
  ///调用 updated 方法
  updated(oldWidget);
  _dirty = true;
  rebuild();
}
@protected
void updated(covariant ProxyWidget oldWidget) {
   /// 通知Clients ;   这个方法在ProxyElement中是空的
    notifyClients(oldWidget);
}

ProxyElement最终会调用 notifyClients方法,这个方法在 InhertedElement中进行了实现。

/// from InhertedElement
  /// 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);
    }
  }
}
​
 /// from  InhertedElement
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    ///调用 element的 didChangeDependencies 方法;
    dependent.didChangeDependencies();
  }
​
  ///from Element
  @mustCallSuper
  void didChangeDependencies() {
    assert(_active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    /// 标记需要重新进行绘制;
    markNeedsBuild();
  }
​
​

总结一句话:重新构建的时候,InheritedElement将_dependencies中的ELement全部标记为 重新构建。这样,当 InheritedWidget的属性发生变化的时候,便会很快的有目标的将使用到它属性的Widget进行更新。

这也是为什么很多基于组件基于InheritedWidget,这样的效率很高,而且使用起来很方便。一些状态管理框架也是基于InheritedWIdget进行了一定的封装。

PS:_inheritedWidgets的注册和继承

上面有一点我们是一句话带过的 :在FrogColorElement创建的时候会将其加入到 _inheritedWidgets 中。 这里我们需要分析 Element的mount方法中执行了什么操作;

/// from Element 
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
   ////---省略
  ///最终调用
  _updateInheritance();
  assert(() {
    _debugLifecycleState = _ElementLifecycle.active;
    return true;
  }());
}
​
  /// from Element  若不是InheritedElement的情况下,会查找父级的 _inheritedWidgets
  void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
​
/// from InheritedElement 若是InheritedElement的情况下 
@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;
  }
​

总结:通过 _updateInheritance 查找上级的 _inheritedWidgets 若本Element是InheritedElement那么将自己加入进去。