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

本文翻译自 Flutter 官方推荐的文章:Managing Flutter Application State With InheritedWidgets。通过官网文档或推荐文章,能够让我们更好地了解 Flutter 的状态管理机制。
前言
通常来说,交互式应用可以分为三个部分:Model,View 和 Controller,也就是我们常说的 MVC 模式。使用过 Flutter 样例的人会对使用Widget和回调方式来构建视图和控制器的响应式方式很熟悉。但是,对于 Model 这一层来说,确未必那么清晰。Flutter 的 Model 层实际代表了其保持的状态。Widget 为状态提供了可视化的呈现,并且允许用户修改它。当widget的build方法从 Model 中获取值时,或者回调函数修改 Model 值的时候,widget将会随着 Model 的改变而重新构建。本篇文章就是介绍这一切是怎么发生的。本篇文章回顾了 Flutter 的有状态组件和 InheritedWidget 类如何将应用的可视化元素绑定到 Model 上。并且引入了一个可以轻松植入应用的ModelBinding类。
声明
本篇介绍的构建 MVC 应用的方式并不是唯一的。如果你要构建大型的应用的话,有很多种方式可以将 Flutter 绑定到模型上。本篇结尾也会列出其中的一些。换言之,即便你决定最后不使用 ModelBinding 类,你也可以收获到 Flutter 的状态管理机制。
这并不是面向初学者的文章,你至少需要对 Flutter 的 API 有一定的了解,例如:
你能够熟练地使用 Dart 编写类,并且了解
==操作符和哈希码重载,以及泛型方法。对基本的 Flutter 组件类熟悉,并且懂得如何自己写一个新的组件。
应用的模型
为了展示本篇的示例,我们需要一个样例应用模型。为了聚焦,我们会将这个模型设计得尽可能地简单。在我们的模型类中只有一个值,以及包括了操作符==和 hashCode 重载。
当然,这个模型也可以根据应用的实际情况进行扩展。注意,这是一个不可变的模型,因此如果要改变的只能是替换它。下面的 MVC 方式也可以用可变的模型,但是那样会稍微有点复杂。
将模型与有状态组件绑定
这是集成模型最简单的方式,对于只需要一个下午就搞定的应用来说非常合适。有状态组件会与一个保持状态的 State 对象关联。这个 State 对象会的 build 的方法会构建该组件的子组件树,就像无状态组件的build 方法一样。调用State 对象的 setState 方法时,在间隔一个显示桢切换的时间间隔后,将会触发组件重新构建。如果有状态组件的状态对象持有模型,那么用于配置它的build方法在调用 setState 方法时,就会使用模型的值。下面的这个有状态组件十分简单,只是持有了一个 ViewModel 对象,然后提供了一个 update 方法来更新模型。
使用有状态组件绑定的限制
使用上面的方式写代码的肯定是个草包(😂这话不是我说的,误伤了请自醒——我们之前的示例代码也是这么写的,确实很初级)。对于相对大规模的应用来说,就很不适用了。具体来说,会有如下的缺陷:
当模型改变的时候,整个
ViewController以及整个组件树都会被重建,而不仅仅是依赖模型的那个组件(即Text)。如果子组件需要使用模型的值,模型只能通过构造器参数传递,或者使用回调闭包。
如果再往下层级的组件需要使用模型的值,那只能是沿着组件树的链条一层层往下传递模型对象。
如果子组件需要修改模型的值,那就必须调用
ViewController传递的回调函数。这个例子中就如同RaisedButton的onPressed方法那样。
因此,有状态组件其实更适用于创建自己内部的状态,而不适用于在复杂应用中共享数据模型。而对应程序员来说,搞定复杂的事情才让我们显得更有价值。
版本 0:使用 InheritedWidget 绑定模型
InheritedWidget 类有一些特殊的使得它很适合在组件树里共享模型。
给定一个
BuildContext,查找一个特定类型的最近的InheritedWidget的祖先节点十分便捷,只需要按表查找就行。InheritedWidget会跟踪他们的依赖,例如用于访问InheritedWidget的BuildContext。当一个InheritedWidget重建时,所有它依赖的对象都会被重建。
实际上你很可能已经接触过InheritedWidget 了,例如 Theme 这个组件。Theme.of(context)方法会返回 Theme 的 ThemeData 对象,并且将 context 当做是Theme的一个依赖对象。如果 Theme 对象被重建,并且使用了不同的 ThemeData 值,那么所有依赖于 Theme.of()的组件都会被自动重建。
使用自定义的 InheritedWidget 子类可以按相同的方式实现应用模型的宿主。这里,我们把这个子类称之为 ModelBinding,因为它将应用的组件和模型关联在一起了。
updateShouldNotify 方法在 ModelBinding 被重建时会被调用。如果返回值是 true,那么依赖它的全部组件都会被重建。
BuildContext inheritFromWidgetOfExactType()方法用于查找一个 InheritedWidget 。由于这个方式有点丑,我们稍后再来介绍它。通常,这个方法是使用静态方法包裹。将查找方法加入到ViewModel能够使得任何依赖 ModelBinding 对象的下级组件都可以通过 Model.of(context)方法获取到 ViewModel 对象。
任何 ModelBinding 的下级都可以这么做,而无需一层层传递 ViewModel 对象了。如果ViewModel 对象发生了改变,下级组件就像 ViewController 一样自动被重建。
ModelBinding 所在的组件自身必须是有状态组件。为了更改 ViewModel 对象,该组件还是需要调用 setState 方法。这里我们使用了一个 StateViewController 有状态组件来持有模型对象,而更新Model 对象的方法被当做回调函数传递给了 ViewController。
这种情况下,ViewModel 类是简单的不可变对象,因此只需要赋值一个新的ViewModel对象替换即可完成更新。替换该对象也可能更复杂,例如如果对象引用的 对象需要进行生命周期管理,那么替换该模型的时候可能会需要销毁部分旧的对象。
这个版本的缺陷
运行结果就不贴图了,就是点击按钮数字自动加 1,代码已提交至:Flutter 状态管理代码。这个版本从好的方面来看,这个版本的 ModelBinding 类使得组件很容易获取模型对象,并且当模型改变的时候可以自动重建。
但是,反过来,这个版本还需要使用 updateModel 回调方法沿着组件树传递到实际控制状态改变的组件,这种方式的代码并不好维护。下一个版本我们来实现一个更通用的 ModelBinding 类,是的子组件可以直接通过ModelBinding提供的 update 方法更新ViewModel 对象。
总结
本篇介绍了 Flutter 应用中的 MVC 模型,对于 Flutter 而言,应用中模型实际上就是组件的状态。如果直接通过一层层的状态传递去控制组件树的下级组件的显示,将会导致代码耦合严重。因此,本篇引入了一个 ModelBinding 类,通过继承 InheritedWidget来实现子组件可以直接访问上级组件的状态,从而避免了状态参数的层层传递。当然,这个版本还存在一个缺陷,那就是更改状态的回调方法还是需要沿着组件树传递,这个我们在下篇会改造一个更通用的 ModelBinding 类。
版权声明: 本文为 InfoQ 作者【岛上码农】的原创文章。
原文链接:【http://xie.infoq.cn/article/d61328588d0ddd3fc97519c06】。文章转载请联系作者。










评论