Flutter状态管理学习(零):InheritedWidget

jkouu 163 0

Flutter的状态管理是开发Flutter应用必须要面对的一个问题。由于Flutter还是一个很新的技术,虽然目前有很多的状态管理框架,但是因为发展的时间都不长,所以它们的优点和相对的不足都比较突出。这个新的系列就是来向大家介绍目前Flutter主流的一些状态管理的解决方案,希望大家能够从中有所收获。

这篇文章是状态管理系列的第零篇。为什么是第零篇呢?因为文章要介绍的InheritedWidget虽然是状态管理的一种解决方案,但是它仅仅是Flutter本身提供的一个简单的组件,并不是一个完整的框架(或者说功能不强,实现比较底层)。但是,作为Flutter自己提供的一种解决方案,它又是我们要学习其它框架时必须要了解的内容(因为它涉及到Flutter内部状态传递的实现)。

废话就先说到这,我们进入正题。

1.InheritedWidget的使用

void main() => runApp(new DemoApp());

class DataRepo extends InheritedWidget {
  DataRepo({Key key, this.data, Widget child})
      : super(key: key, child: child);

  final String data;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return oldWidget.data != welcomeInfo;
  }
}

class DataConsumer extends StatelessWidget {
  @override
  build(BuildContext context) {
    final DataRepo repo = context.inheritFromWidgetOfExactType(DataRepo);
    return Text(widget.data);
  }
}

class DemoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter InheritWidget',
      home: DataRepo(
          data: 'hello flutter',
          child: Center(
            child: DataConsumer(),
          ),
      ),
    );
  }
}

上面是一个简单的使用InheritedWidget的Demo。代码也很好懂。我们用InheritedWidget作为一个数据仓库,里面存储了要被其它组件使用的数据——String类型的data。之后我们用一个StatelessWidget作为数据的消费者,消费者首先通过context获取InheritedWidget的实例,然后通过这个实例取出存储的数据并使用。

下面我们就根据这个Demo来学习InheritedWidget的实现。(下面的内容需要对BuildContext、Element有一定了解,如果还不了解它们,可以看我写的这篇文章:https://jkouu.cn/index.php/flutter-page/)

2.InheritedWidget的实现

首先我们先看一下InheritedWidget的源码。

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => new InheritedElement(this);
  
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

作为一个和StatelessWidget、StatefulWidget同级的抽象类,InheritedWidget的源码也只有简单的三个函数。除去构造函数,实际上值得我们进一步学习的只有createElement和updateShouldNotify这两个函数。

2.1.InheritedElement

首先我们看一下InheritedElement。和别的Element相比,InheritedElement的主要区别在于_updateInheritance方法的实现。

//普通Element的实现
void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
 
  //InheritedElement的实现 
  void _updateInheritance() {
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = new HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  }

上面是普通的Element和InheritedElement关于_updateInheritance方法不同实现的对比。首先我们先解释一下函数中出现的两个成员变量:_inheritedWidgets和_parent。

_parent很简单,就是Element在Element树上的父节点。

_inheritedWidgets是一个HashMap,它的类型为HashMap<Type, InheritedElement>。可以看到,这个属性就是专门用来管理InheritedElement的。

普通Element的_updateInheritance函数实现很简单:如果父节点的_inheritedWidgets不为空,那么就继承它的_inheritedWidgets,否则就令自己的_inheritedWidgets为空。InheritedElement的_updateInheritance函数实现其实也不难,就是在继承父节点的_inheritedWidgets的情况下,再把自己加入这个Map中。这样,所有的InheritedElement就会通过_updateInheritance这个方法从Element的树根层层下沉到树的各个节点,这样所有的Element就可以通过自己的_inheritedWidgets来获取到指定的InheritedWidget的数据了。

这里再说一个小问题:_updateInheritance函数是在什么时候执行的呢?它是在Element的mount和active阶段被调用的。关于Element的生命周期,可以查看谷歌官方给出的介绍,我觉得已经很详细了:https://api.flutter.dev/flutter/widgets/Element-class.html

2.2.inheritFromWidgetOfExactType

前面我们说到,Element可以通过自己的_inheritedWidgets来获取到指定的InheritedWidget,那么我们应该怎么获取呢?从Demo中我们看到,调用的是context提供的inheritFromWidgetOfExactType方法。

InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      _dependencies ??= new HashSet<InheritedElement>();
      _dependencies.add(ancestor);
      ancestor._dependents.add(this);
      return ancestor.widget;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

上面是inheritFromWidgetOfExactType函数的代码。因为本身做的就是从HashMap取出Element的简单操作,所以逻辑并没有很复杂。但是我们发现,函数除了执行从HashMap中取出Element的操作,还维护了属于该Element的一个名为_dependents的HashSet。这是做什么用的呢?

2.3.notifyClients

在文章开头的demo中,InheritedWidget的data是不变的。那么如果InheritedWidget的data是可变的呢?显然,在InheritedWidget发生变化后,InheritedElement需要通知所有使用它的Element自己的Widget发生了变化,这就用到了它的notifyClients函数。

void notifyClients(InheritedWidget oldWidget) {
    if (!widget.updateShouldNotify(oldWidget))
      return;

    for (Element dependent in _dependents) {
      dependent.didChangeDependencies();
    }
  }

上面是notifyClients函数的源码。这里我们就看到了_dependents的作用——用来通知其它Element自己发生了变化。至于怎么判定自己是否发生了变化,这就用到了我们自己实现的updateShouldNotify函数了。

3.总结

InheritedWidget的实现比较简单,但是流程又比较碎,所以这里再整理一下文章的内容,用几句话对InheritedWidget做一个总结:

  1. InheritedWidget实际上不是一个UI控件,它更像是一个数据仓库,存储着需要共享给别的控件使用的数据。
  2. 一个程序中的所有InheritedWidget都由一个HashMap维护。这个HashMap从Element树的根节点开始向下传递,使得每个Element都可以通过inheritFromWidgetOfExactType函数获取到在其上层的所有InheritedWidget。
  3. InheritedWidget采用了观察者模式。所有需要使用InheritedElement的Element在调用inheritFromWidgetOfExactType时也会将自己注册在使用的InheritedElenemt的观察者中。
  4. InheritedElement通过updateShouldNotify来判断是否通知所有的观察者自己发生了变化。观察的Element在收到通知后调用自己的didChangeDependencies函数来针对变化做相应的逻辑处理。

发表评论 取消回复
您必须 [登录] 才能发表评论!
分享