写点什么

深入了解 Flutter 的状态管理机制(上)

作者:岛上码农
  • 2022 年 5 月 09 日
  • 本文字数:3838 字

    阅读完需:约 13 分钟

深入了解 Flutter 的状态管理机制(上)

本文翻译自 Flutter 官方推荐的文章:Managing Flutter Application State With InheritedWidgets。通过官网文档或推荐文章,能够让我们更好地了解 Flutter 的状态管理机制。

前言

通常来说,交互式应用可以分为三个部分:ModelViewController,也就是我们常说的 MVC 模式。使用过 Flutter 样例的人会对使用Widget和回调方式来构建视图和控制器的响应式方式很熟悉。但是,对于 Model 这一层来说,确未必那么清晰。Flutter 的 Model 层实际代表了其保持的状态。Widget 为状态提供了可视化的呈现,并且允许用户修改它。当widgetbuild方法从 Model 中获取值时,或者回调函数修改 Model 值的时候,widget将会随着 Model 的改变而重新构建。本篇文章就是介绍这一切是怎么发生的。本篇文章回顾了 Flutter 的有状态组件和 InheritedWidget 类如何将应用的可视化元素绑定到 Model 上。并且引入了一个可以轻松植入应用的ModelBinding类。

声明

本篇介绍的构建 MVC 应用的方式并不是唯一的。如果你要构建大型的应用的话,有很多种方式可以将 Flutter 绑定到模型上。本篇结尾也会列出其中的一些。换言之,即便你决定最后不使用 ModelBinding 类,你也可以收获到 Flutter 的状态管理机制。


这并不是面向初学者的文章,你至少需要对 Flutter 的 API 有一定的了解,例如:

  • 你能够熟练地使用 Dart 编写类,并且了解==操作符和哈希码重载,以及泛型方法。

  • 对基本的 Flutter 组件类熟悉,并且懂得如何自己写一个新的组件。

应用的模型

为了展示本篇的示例,我们需要一个样例应用模型。为了聚焦,我们会将这个模型设计得尽可能地简单。在我们的模型类中只有一个值,以及包括了操作符==hashCode 重载。

class ViewModel {  const ViewModel({ this.value = 0 });
final int value;
@override bool operator ==(Object other) { if (identical(this, other)) return true; if (other.runtimeType != runtimeType) return false; final ViewModel otherModel = other; return otherModel.value == value; }
@override int get hashCode => value.hashCode;
static ViewModel of(BuildContext context) { final ModelBinding binding = context.dependOnInheritedWidgetOfExactType(aspect: ModelBinding); return binding.model; }}
复制代码


当然,这个模型也可以根据应用的实际情况进行扩展。注意,这是一个不可变的模型,因此如果要改变的只能是替换它。下面的 MVC 方式也可以用可变的模型,但是那样会稍微有点复杂。

将模型与有状态组件绑定

这是集成模型最简单的方式,对于只需要一个下午就搞定的应用来说非常合适。有状态组件会与一个保持状态的 State 对象关联。这个 State 对象会的 build 的方法会构建该组件的子组件树,就像无状态组件的build 方法一样。调用State 对象的 setState 方法时,在间隔一个显示桢切换的时间间隔后,将会触发组件重新构建。如果有状态组件的状态对象持有模型,那么用于配置它的build方法在调用 setState 方法时,就会使用模型的值。下面的这个有状态组件十分简单,只是持有了一个 ViewModel 对象,然后提供了一个 update 方法来更新模型。


class ViewController extends StatefulWidget {  _ViewControllerState createState() => _ViewControllerState();}
class _ViewControllerState extends State<ViewController> { Model currentModel = ViewModel(); void updateModel(ViewModel newModel) { if (newModel != currentModel) { setState(() { currentModel = newModel; }); } } @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { updateModel(ViewModel(value: currentModel.value + 1)); }, child: Text('Hello World ${currentModel.value}), ); }}
复制代码

使用有状态组件绑定的限制

使用上面的方式写代码的肯定是个草包(😂这话不是我说的,误伤了请自醒——我们之前的示例代码也是这么写的,确实很初级)。对于相对大规模的应用来说,就很不适用了。具体来说,会有如下的缺陷:


  • 当模型改变的时候,整个 ViewController 以及整个组件树都会被重建,而不仅仅是依赖模型的那个组件(即 Text)。

  • 如果子组件需要使用模型的值,模型只能通过构造器参数传递,或者使用回调闭包。

  • 如果再往下层级的组件需要使用模型的值,那只能是沿着组件树的链条一层层往下传递模型对象。

  • 如果子组件需要修改模型的值,那就必须调用 ViewController 传递的回调函数。这个例子中就如同 RaisedButtononPressed 方法那样。


因此,有状态组件其实更适用于创建自己内部的状态,而不适用于在复杂应用中共享数据模型。而对应程序员来说,搞定复杂的事情才让我们显得更有价值

版本 0:使用 InheritedWidget 绑定模型

InheritedWidget 类有一些特殊的使得它很适合在组件树里共享模型。


  • 给定一个 BuildContext,查找一个特定类型的最近的InheritedWidget 的祖先节点十分便捷,只需要按表查找就行。

  • InheritedWidget 会跟踪他们的依赖,例如用于访问InheritedWidgetBuildContext。当一个 InheritedWidget 重建时,所有它依赖的对象都会被重建。


实际上你很可能已经接触过InheritedWidget 了,例如 Theme 这个组件。Theme.of(context)方法会返回 ThemeThemeData 对象,并且将 context 当做是Theme的一个依赖对象。如果 Theme 对象被重建,并且使用了不同的 ThemeData 值,那么所有依赖于 Theme.of()的组件都会被自动重建。


使用自定义的 InheritedWidget 子类可以按相同的方式实现应用模型的宿主。这里,我们把这个子类称之为 ModelBinding,因为它将应用的组件和模型关联在一起了。


class ModelBinding extends Inherited {  ModelBinding({    Key key,    this.model = const ViewModel(),    Widget child,  }): assert(model != null), super(Key: key, child:child);    final ViewModel model;    @override  bool updateShouldNotify(ModelBinding oldWidget) => model != oldWidget.model;}
复制代码


updateShouldNotify 方法在 ModelBinding 被重建时会被调用。如果返回值是 true,那么依赖它的全部组件都会被重建。


BuildContext inheritFromWidgetOfExactType()方法用于查找一个 InheritedWidget 。由于这个方式有点丑,我们稍后再来介绍它。通常,这个方法是使用静态方法包裹。将查找方法加入到ViewModel能够使得任何依赖 ModelBinding 对象的下级组件都可以通过 Model.of(context)方法获取到 ViewModel 对象。


// 现在在 ModelBinding 的下级组件可以通过 Model.of(context)访问了Text('Hello WOrld ${ViewModel.of(context).value}')
复制代码


任何 ModelBinding 的下级都可以这么做,而无需一层层传递 ViewModel 对象了。如果ViewModel 对象发生了改变,下级组件就像 ViewController 一样自动被重建。


ModelBinding 所在的组件自身必须是有状态组件。为了更改 ViewModel 对象,该组件还是需要调用 setState 方法。这里我们使用了一个 StateViewController 有状态组件来持有模型对象,而更新Model 对象的方法被当做回调函数传递给了 ViewController


class StateViewController extends StatefulWidget {  StateViewController({Key key}) : super(key: key);
@override _StateViewControllerState createState() => _StateViewControllerState();}
class _StateViewControllerState extends State<StateViewController> { ViewModel currentModel = ViewModel();
void _updateModel(ViewModel newModel) { setState(() { currentModel = newModel; }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('模型绑定版本0'), ), body: Center( child: ModelBinding( model: currentModel, child: ViewController(_updateModel), ), ), ); }}
复制代码


这种情况下,ViewModel 类是简单的不可变对象,因此只需要赋值一个新的ViewModel对象替换即可完成更新。替换该对象也可能更复杂,例如如果对象引用的 对象需要进行生命周期管理,那么替换该模型的时候可能会需要销毁部分旧的对象。

这个版本的缺陷

运行结果就不贴图了,就是点击按钮数字自动加 1,代码已提交至:Flutter 状态管理代码。这个版本从好的方面来看,这个版本的 ModelBinding 类使得组件很容易获取模型对象,并且当模型改变的时候可以自动重建。


但是,反过来,这个版本还需要使用 updateModel 回调方法沿着组件树传递到实际控制状态改变的组件,这种方式的代码并不好维护。下一个版本我们来实现一个更通用的 ModelBinding 类,是的子组件可以直接通过ModelBinding提供的 update 方法更新ViewModel 对象。

总结

本篇介绍了 Flutter 应用中的 MVC 模型,对于 Flutter 而言,应用中模型实际上就是组件的状态。如果直接通过一层层的状态传递去控制组件树的下级组件的显示,将会导致代码耦合严重。因此,本篇引入了一个 ModelBinding 类,通过继承 InheritedWidget来实现子组件可以直接访问上级组件的状态,从而避免了状态参数的层层传递。当然,这个版本还存在一个缺陷,那就是更改状态的回调方法还是需要沿着组件树传递,这个我们在下篇会改造一个更通用的 ModelBinding 类。


发布于: 刚刚阅读数: 2
用户头像

岛上码农

关注

用代码连接孤岛,公众号@岛上码农 2022.03.03 加入

从南漂到北,从北漂到南的业余码农

评论

发布
暂无评论
深入了解 Flutter 的状态管理机制(上)_flutter_岛上码农_InfoQ写作社区