Flutter InheritedWidget源码解析

介绍

InheritedWidget是flutter中用来从上层传递数据到下层的组件(类似于React中的Context)。对于简单的情况,如果需要将数据传递到下层,可以通过参数的方式。如下所示

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层级很多,就需要从上层一层一层的向下传递,容易造成混乱。所以flutter提供了InheritedWidget方便上层Widget跨层级为下层子节点提供数据。

使用方式

可以创建一个Widget继承自InheritedWidget, 在Widget中保存需要共享给子Widget的值。创建一个静态方法(名称通常为of),该方法接收BuildContext参数,通过调用BuildContextdependOnInheritedWidgetOfExactType可以从下层找到该Widget,从而获取Widget中的值。

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(FrogColor old) => color != old.color;
 }

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

下层获取可以通过of方法获取值

    Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: FrogColor(
          color: Colors.green,
          child: Center(
            child: ChildWidget(),
          )
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  class ChildWidget extends StatelessWidget {

      Widget build(BuildContext context) {
        var color = FrogColor.of<FrogColor>(context).color;
        return Text(
            "color from parent",
            style: TextStyle(
                color:color
            ));
      }
  }

可以看到,在子Widget中并没有设置color参数,而是通过FrogColor.of(context).color获取到了祖先Widget的值,即使中间跨越了其他Widget。那么是通过什么来访问到祖先Widget的数据呢,关键点就是dependOnInheritedWidgetOfExactType方法。

源码分析

该方法在BuildContext中,而BuildContext的实现类是Element,我们来看下这个方法的源码。(还有一个inheritFromWidgetOfExactType方法,这个方法的实现和dependOnInheritedWidgetOfExactType一样,在1.12版本中被deprecated了)

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

方法非常简单,首先使用泛型T作为参数(Dart中的泛型比Java强大一些,没有被擦除),从_inheritedWidgets中获取类型为InheritedElementancestor对象,该对象就是InheritedWidget所对应的Element对象。如果这个ancestor对象不为空,再调用dependOnInheritedElement方法。

Map<Type, InheritedElement> _inheritedWidgets;

_inheritedWidgets是在Element类中定义的一个Map, 保存了所有的所有的祖先InheritedWidget。这个_inheritedWidgets是从哪里初始话的呢?

Element中的_inheritedWidgets初始化流程

通过搜索可以找到在_updateInheritance方法中会更新_inheritedWidgets Map,_updateInheritance是也是在Element类中的定义,默认是实现是将parent的值赋给自己。

void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

在InheritedElement中重写了_updateInheritance方法,不会直接赋值,而是通过HashMap.from方法重新创建了一个Map,并且通过_inheritedWidgets[widget.runtimeType] = this; 将InheritedElement自己保存到这个Map中。

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

通过上面的方式,就可以一层一层的传递保存InheritedEelement的Map,非InheritedWidget直接保存parent的_inheritedWidgets的引用,是同一个Map对象。而InheritedWidget对应的InheritedElement会创建一个新的Map,并将对应widget的runtimeType作为key,element作为Value保存在Map中。所以底层的Element的_inheritedWidgets Map中包含所有类型为InheritedWidget的祖先节点信息。就可以通过Widget 的Type访问到Element,再通过Element访问到Widget。

_updateInheritance方法在Element被挂载到Element Tree时(mount)或者activate的时候被调用。

void mount(Element parent, dynamic newSlot) {
    ...
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
    ...
  }

   void activate() {
    ...
    final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
    _active = true;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    ...
    if (_dirty)
      owner.scheduleBuildFor(this);
    if (hadDependencies)
      didChangeDependencies();
  }

dependOnInheritedElement

分析完_inheritedWidgets的初始化流程,我们回头再看dependOnInheritedElement方法。

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

dependOnInheritedElement中首先会将ancestor保存在_dependencies中,再调用ancestor
updateDependencies方法。

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

updateDependencies方法再调用setDependencies方法

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

 void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

setDependencies方法底层的Element保存在_dependents Map中。

以上就是通过BuildContext的dependOnInheritedWidgetOfExactType访问到祖先InhritedWidget的过程。

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中,Elemment树中的每一个节点都会保存祖先节点的InheritedWidget信息,所以可以在子组件中可以查询到特定类型的InheritedWidget,并获取到其所保存的值。当InheritedWidget被重新build时,会根据updateShouldNotify方法决定是否通知依赖该InheritedWidget的子Widget更新。

在flutter framework源码中大量使用了InheritedWidget实现数据的传递和共享,通过源码分析可以对其原理进行深入的理解但在我们日常开发中,还是推荐使用Provider,Provider提供了更简单的API和更强大的功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。