深入了解 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】。文章转载请联系作者。
评论