书接上回,我们通过InheritedWidget实现了跨Widget的数据管理。
可以发现,在使用InheritedWidget来实现数据管理的方式中,有几个东西是必须的。
InheritedWidget
数据对象
管理InheritedWidget的StatefulWidget
展示View
在上篇文章中,我们使用了一个StatefulWidget来管理InheritedWidget,借助StatefulWidget的State来完成数据修改的能力,但是这种方式在使用的过程中,会发现有一些问题。
业务逻辑与StatefulWidget耦合
模板代码太多,写起来复杂
所以,针对上面的这些问题,实际上在封装InheritedWidget进行数据管理的时候,通常会根据职责,将代码分为几个部分。
这实际上和Android中的MVVM模式比较类似,但是由于Android原生没有响应式的能力,所以在Android上的MMVM,基本都是借助Rx或者Jetpack的方式来实现,在Flutter中,官方也没有给出一个标准的MVVM示例,其实采用哪种模式并不是关键,每个人对设计模式的理解都不相同,针对业务场景的实现方式也会有不同,所以「不管黑猫白猫,抓到老鼠就是好猫」。
下面笔者就展示一种基于InheritedWidget的封装方案。
首先,定义数据Model,它是交互数据的抽象。这里简单的使用一个类的表示。
class CustomModel {
const CustomModel({this.value = 0});
final int value;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
final CustomModel otherModel = other;
return otherModel.value == value;
}
@override
int get hashCode => value.hashCode;
}
这里的Model和普通的Model相比,仅仅是重新了「==」操作符,至于为什么,后面就知道了。
接下来,同样是使用StatefulWidget来管理InheritedWidget,同时,为了更加通用,在类中增加的泛型的约定。
首先是InheritedWidget,由于它不会被外界所感知,所以设计为私有的。
class _ModelBindingScope extends InheritedWidget {
const _ModelBindingScope({Key key, this.modelBindingState, Widget child}) : super(key: key, child: child);
final _ModelBindingState modelBindingState;
@override
bool updateShouldNotify(_ModelBindingScope oldWidget) => true;
}
然后是StatefulWidget,它实际上相当于一层胶水,粘合了InheritedWidget和View。
在前面的文章中,对Model的处理也是在StatefulWidget的State中的,这就导致了这层胶水不够通用,所以,这里的设计思路是尽可能将这层胶水设计的更加通用。
class ModelBinding extends StatefulWidget {
ModelBinding({
Key key,
@required this.initialModel,
this.child,
}) : assert(initialModel != null),
super(key: key);
final T initialModel;
final Widget child;
_ModelBindingState createState() => _ModelBindingState();
static T of(BuildContext context) {
final _ModelBindingScope scope = context.dependOnInheritedWidgetOfExactType<_ModelBindingScope>();
return scope.modelBindingState.currentModel;
}
static void update(BuildContext context, T newModel) {
final _ModelBindingScope scope = context.dependOnInheritedWidgetOfExactType<_ModelBindingScope>();
scope.modelBindingState.updateModel(newModel);
}
}
class _ModelBindingState extends State> {
T currentModel;
@override
void initState() {
super.initState();
currentModel = widget.initialModel;
}
void updateModel(T newModel) {
if (newModel != currentModel) {
setState(() => currentModel = newModel);
}
}
@override
Widget build(BuildContext context) {
return _ModelBindingScope(
modelBindingState: this,
child: widget.child,
);
}
}
这个设计的核心,实际上就是这个胶水——ModelBinding,它实际上也分为两部分,即StatefulWidget和State,在StatefulWidget中,暴露了of和update两个函数,其中of函数,我们比较熟悉了,主要是update函数,它调用的是State中的updateModel函数,而这个函数,做了一个通用的处理,也就是用一个全新的model,替换当前的model。
void updateModel(T newModel) {
if (newModel != currentModel) {
setState(() => currentModel = newModel);
}
}
这也就是为什么这个粘合剂可以设计的更加通用的原因。
最后来看下如何使用。
class InheritedWidgetPattern extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ModelBinding(
initialModel: const CustomModel(),
child: Column(
children: [
MainTitleWidget('InheritedWidget使用的一般范式'),
View2(),
View1(),
],
),
);
}
}
class View1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CustomModel model = ModelBinding.of(context);
return RaisedButton(
onPressed: () {
ModelBinding.update(context, CustomModel(value: model.value + 1));
},
child: Text('Hello World ${model.value}'),
);
}
}
class View2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Show text ${ModelBinding.of(context).value}');
}
}
代码地址:Flutter Dojo-Backend-InheritedWidgetPattern
通过ModelBinding对需要进行管理的数据-CustomModel、以及需要这些数据的View—View1和View2,在View中,通过 ModelBinding.of
这种设计有两个需要注意的地方。
在这种情况下,数据与View一样都是无状态的,每一次数据改动,都是使用新的Model替换原有的Model
Dart的垃圾回收策略可以保证这种Model替换的算法是高效的(Mark-Swap)、且不会存在线程安全问题
但是,这种封装方式一定好吗,仁者见仁智者见智