这篇文章是Flutter state management系列的一部分。讨论的是在Flutter中是如何原生支持状态管理,当然,这部分API可能比较low-level,但是pub.dev上一些受欢迎的状态管理库(比如 provider)的底层都是基于此,所以了解其实现原理相当重要,这也是这篇文章的重点。

前言 Link to heading

作者原来从事Android开发,习惯了使用findViewById这种在命令式UI编程(imperative style of UI programming)环境下数据共享(Flutter中称作共享状态shared state)的方式,但Flutter采用的是声明式UI编程(declarative style of UI programming),没有习惯的 findViewById 或者 Text.setText(),所以转换原来的思维,用新的思想去考虑这种编程范式,寻找合适的状态管理模式。

状态 Link to heading

UI = f( state ),这个有趣的公式的解释了为什么Flutter是声明式的,描述了 stateui 之间的关系,这表示Flutter会构建其用户界面以反映应用的当前状态。Flutter将状态分为两类:暂时状态(Ephemeral state)和 App状态(App state),前者往往只存在与单一控件中,随控件的销毁而回收,不需要与其他控件共享其状态,这不需要使用状态管理技术;后者与前者相反,其往往可能需要在应用的多个部分之间共享,观察(订阅)/通知更新是常见的操作,比如说电商应用的购物车,在购物页面可以将需要购买的商品放入购物车中,在购物车页面可以进行删除商品,在支付页面需要计算购物车中的所有商品的价格,这都需要利用状态管理技术才能优雅的实现。

简单用法 Link to heading

在这里,我们举一个比较简单例子来说明InheritedModel是如何使用的,然后从用法了解其底层实现原理。我们将利用Flutter 默认的Application项目,但是我们修改其AppBartitle的来源:从原来的从构造器传入修改为从父控件(这里指 InheritedModel)中获取,因为InheritedModel间接继承于Widget,所以它其实就是一个Widet,可以用来构建UI tree中的一个Element。这里,我们将title存入新创建的InheritedModel的子类MyModel中,这样所有的children都可以获取到这个title,在这里,这个title只用于AppBar

class MyModel extends InheritedModel<String> {  
  const MyModel(this.title, {Key key, Widget child})  
      : super(key: key, child: child);  
  
  /// AppBar title  
  final String title;  
  
 static MyModel of(BuildContext context) {  
    return InheritedModel.inheritFrom<MyModel>(context, aspect: null);  
  }  
  
  @override  
  bool updateShouldNotify(InheritedWidget oldWidget) {  
    return true;  
  }  
  
  @override  
  bool updateShouldNotifyDependent(  
      InheritedModel<String> oldWidget, Set<String> dependencies) {  
    return true;  
  }  
}

class MyApp extends StatelessWidget {  
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context) {  
    return MaterialApp(  
      title: 'Flutter Demo',  
      theme: ThemeData(...),  
      home: MyModel('Flutter Demo Home Page', child: MyHomePage()),  
    );  
  }  
}  
  
class MyHomePage extends StatefulWidget {  
  MyHomePage({Key key}) : super(key: key);  
  
  @override  
  _MyHomePageState createState() => _MyHomePageState();  
}  
  
class _MyHomePageState extends State<MyHomePage> {  
  ... 
  @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
      appBar: AppBar(  
        title: Text(MyModel.of(context).title),  
   )
   ...
     );  
  }  
}

如何从 Child Widget 获取到 指定 Parent Widget? Link to heading

要使你需要共享的 Parent Widget能够被它的Children发现,你的 Parent Widget需要继承自InheritedWidget(当然,InheritedModel 也是继承自InheritedWidget),InheritedWidget的官方解释是Base class for widgets that efficiently propagate information down the tree.,那它是如何传播信息的呢?正向的思路不太容易知道,但是我们可以反向来,我们研究从子控件是如何获取上层控件反推,就可以发现其所以然。 在子控件中是通过MyModel.of(context)这样一个 model-specific static of method 获取MyModel的,看到这种方法定义,想必每个Flutter开发者都很面熟,的确,这种状态管理技术已经广泛的应用到Flutter framework中,例如Theme.of(context)MediaQuery.of(context),这应该算作是一种规范,应该是所有Flutter开发者都应该遵守的。

static MyModel of(BuildContext context) {  
  return InheritedMdel.inheritFrom<MyModel>(context, aspect: null);  
}

这个静态方法内部调用了InheritedModel的泛型静态方法inheritFrom,我们先忽略第二个参数。

static T inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object aspect }) {  
    if (aspect == null)  
      return context.dependOnInheritedWidgetOfExactType<T>();  
    ...   
}

aspectnull的时候,它又调用了BuildContext的成员方法dependOnInheritedWidgetOfExactType,继续进入这个方法,发现它定义在Element中,在这里我们其实可以猜测一个其他的东西,我们所用到的BuildContext其实都是Element的实现,通过下面的源码证实了这一点:

class: State

/// The location in the tree where this widget builds.  
///  
/// The framework associates [State] objects with a [BuildContext] after  
/// creating them with [StatefulWidget.createState] and before calling  
/// [initState]. The association is permanent: the [State] object will never  
/// change its [BuildContext]. However, the [BuildContext] itself can be moved  
/// around the tree.  
///  
/// After calling [dispose], the framework severs the [State] object's  
/// connection with the [BuildContext].  
BuildContext get context => _element;  
StatefulElement _element;
class: StatefulElement

@override  
Widget build() => _state.build(this);

这不是这篇文章的重点,我们继续查看dependOnInheritedWidgetOfExactType的实现,方法的第二行是说要从_inheritedWidgets这个Map中根据Key去获取对于的InheritedElement,注意,这里至关重要,这是我们反推的开始点,我们在继续查看下面的逻辑,如果找到,则相互添加自己为依赖,最后,从查到的这个InheritedElement中获取Widget并作强制类型转换返回。

class: Element

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

现在我们开始从上述提到的关键点开始反推,往_inheritedWidgets这个Map中设置数据是开始点,在整个Element类中只有一个方法是用来给_inheritedWidgets设置数据的:

class: Element

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

这个方法调用出在mount方法

@mustCallSuper  
void mount(Element parent, dynamic newSlot) {  
  assert(_debugLifecycleState == _ElementLifecycle.initial);  
 assert(widget != null);  
 assert(_parent == null);  
 assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);  
 assert(slot == null);  
 assert(depth == null);  
 assert(!_active);  
  _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();  
 assert(() {  
    _debugLifecycleState = _ElementLifecycle.active;  
 return true;  }());  
}

熟悉Flutter UI构建流程的人都知道,这是Element被添加到Tree的入口方法,所以,我们可以清楚的认识到,_inheritedWidgets是在UI Tree构建过程中逐步的从父节点继承下来的,由于我们的MyModel控件是一个InheritedWidget,所以,InheritedElement使用MyModel的来配置,由于InheritedElement是继承自Element,所以它也具有_updateInheritance(),由于它是InheritedElement唯一可提供者,所以它需要override这个方法,提供_inheritedWidgets,供下层节点使用。

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

这样,MyModel下的所有Children都可以获取到MyModel这个Ancestor,貌似有点findViewById的味道。

状态管理参考Flutter 官方文档