Flutter | 状态管理探索篇——Scoped Model(一)
前言
Flutter的很多灵感来自于React,它的设计思想是数据与视图分离,由数据映射渲染视图。所以在Flutter中,它的Widget是immutable的,而它的动态部分全部放到了状态(State)中。
假如你曾进行过react开发,也许你一下会想到Redux。flutter有类似redux的状态管理的库吗?答案是肯定的,但是有关在flutter中使用redux的应用实践我们会在之后的文章中进行介绍。
这个系列将会从这几个状态管理方案进行深入研究:
- Scoped_model
- redux
- BLoC
- 对比总结篇
今天要和大家分享的是第一篇,使用Scoped_model进行状态管理。
为什么需要状态管理
在我们一开始构建应用的时候,也许很简单。我们有一些状态,直接把他们映射成视图就可以了。这种简单应用可能并不需要状态管理。

但是随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样。

这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。
什么是Scoped_model
Scoped_model是一个dart第三方库,提供了让您能够轻松地将数据模型从父Widget传递到它的后代的功能。此外,它还会在模型更新时重新渲染使用该模型的所有子项。
它直接来自于Google正在开发的新系统Fuchsia核心Widgets 中对Model类的简单提取,作为独立使用的独立Flutter插件发布。
实现原理
Scoped model使用了观察者模式,将数据模型放在父代,后代通过找到父代的model进行数据渲染,最后数据改变时将数据传回,父代再通知所有用到了该model的子代去更新状态。
而我们则需要将它们放在顶层入口MaterialApp之上,这样就能进行全局的状态管理了。

Lets do it!
这里我们以一个最简单的CountApp举例,详细介绍Scoped_model的用法。该项目完整代码已放在github仓库。
这是一个在不同页面使用Scoped共享状态信息的app。这两个页面都依赖于一个数字,这个数字会随着我们按下按钮的次数而增加。

第一步:添加依赖
在pubspec中添加scoped_model的依赖。

第二步:创建Model
在Scoped中,Model是一个只包含与状态相关信息的单位。我们应该把状态数据与操作数据的方法抽象出来封装到Model中。
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment(){
_count++;
notifyListeners();
}
}
- 我们需要让我们自定义的CountModel继承至Model。
- 在状态发生变化时(increment)通知所有用到了该model的子项更新状态。(notifyListeners)
第三步:将Model放入顶层
//创建顶层状态
CountModel countModel = CountModel();
@override
Widget build(BuildContext context) {
return ScopedModel<CountModel>(
model: countModel,
child: new MaterialApp(
home: TopScreen(),
),
);
}
- 我们在顶层创建了一个CountModel的实例。
- ScopedModel<T extends Model>是一个StatelessWidget,它接收一个model,并提供给需要它的所有部件。
- 将ScopedModel<T extends Model>的model属性绑定我们的CountModel对象。
第四步:在子页面中获取Model
我们可以从前面的演示图片中看出,一共有两个页面,都使用了同一个model。 Scoped_model提供了两种方式在子页面中获取model。我们先来介绍第一种,使用ScopedModelDescendant获取model。
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Scaffold(
body: Center(
child: Text(
model.count.toString(),
style: TextStyle(fontSize: 48.0),
),
),
);
},
);
}
- ScopedModelDescendant<T extends Model>是一个Stateless Widget,它接收三个参数。
- builder是一个ScopedModelDescendantBuilder,它接收三个参数。
,在builder中能够通过model来获取CountModel实例。
- rebuildOnChange属性能够控制当该状态发生变化时,是否rebuild,作用等同于setState。也就是说我们调用改变状态的一些方法时,不必再setState。
floatingActionButton: new FloatingActionButton(
onPressed: () => model.increment(),
tooltip: 'Increment',
child: new Icon(Icons.add),
)
第二种获取model的方式——使用ScopedModel.of
final countModel = ScopedModel.of<CountModel>(context);
countModel.increment();
或者在Model中重写of方法
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment(){
_count++;
notifyListeners();
}
//重写of方法
CountModel of(context) =>
ScopedModel.of<CountModel>(context);
}
然后直接通过CountModel获取model实例
final countModel2 = CountModel().of(context);
这种方式似乎让我们的代码有更好的可阅读性。
【注意:】我们在使用第二种方式的时候,rebuildOnChange属性默认为false,所以会导致无法刷新(同步)状态的情况发生,需要手动指定rebuildOnChange:true。这里要非常感谢@荣毅coolboy同学的分享!
Q&A
这里看上去似乎只添加了一个model,我应该如何添加多个model
要解决这个问题很简单,使用Mixin!
class MainModel extends Model with AModel,BModel,CModel{}
然后将MainModel放在顶层即可。 这里有一个比较完整的使用ScopedModel管理状态的应用,详细用法可参考该项目。
Scoped是如何做到同步不同页面中的状态的

Scoped如何做到数据能够互相共享的
在不同页面间的数据传递使用了InheritedWidget。

侵入性
由于Model必须继承至Model类,所以它就具有了侵入性。以后假如不用scoped进行状态管理那么必然会带来需要更改多处代码的情况。这并不是我们希望看到的结果。
写在最后
在flutter中,Scoped_model是一种非常简单易上手,并能保持代码高可阅读性的一种新的状态管理方式,值得各位去尝试一下!
本次所用到的代码已经上传Github: github.com/Vadaski/Vad…
如果您对scoped还有任何疑问或者文章的建议,欢迎在下方评论区以及我的邮箱1652219550a@gmail.com与我联系,我会及时回复!
下一章我们将探索Redux在Flutter中的实践,敬请关注。
转载自:https://juejin.cn/post/6844903680844496904