MVC能够说是框架的经典了,可是在MVC框架的实践中,咱们很难作到下降它的耦合度,咱们在使用过程当中,会有大量的接口都出如今controller中,致使controller中的代码很是的庞大,而在view中实现的时候,咱们又习惯性的只实现页面布局相关的东西,而到了动画,页面布局逻辑,咱们又会丢到controller中去处理。controller复杂的逻辑,与页面极高的耦合度,会致使咱们在开发过程没法抽离测试代码,只能经过e2e的方式进行全量测试,增长程序员自测的工做量。 前端
MVVM架构是MVX里面目前来讲最新的一个,让咱们但愿它在出现的时候已经考虑到了MVX模式以前所遇到的问题吧。
在一个前端的角度来说,MVVM是一个再熟悉不过的框架了,毕竟react/vue都是在MVVM框架的基础上出现的,MVVM对于MVC来讲作的最大的改造就是将controller拆解,并分给view和view-model两个部分,经过数据驱动的方式呈现页面,更加的直观。 vue
VIPER 框架,能够说把层次划分到最细,自然的解耦让VIPER代码的测试工做变得异常轻松。
view与view之间是经过router相关联的,没有任何页面之间是强依赖的,这意味着你能够单独测试某张页面而不须要将所有的流程都回归一遍。
并且,viper框架生成的各个组件,均可以认为是一个独立的模块,一个独立的个体,只要你的基础架构相同,那么这些独立模块在任何系统中均可以互相嵌套使用,而不须要作重复工做单独开发这些组件。react
VIPER框架最初起始于iOS设计中,是在MVVM框架的基础上演变而来。git
从字面意思来理解,VIPER 即 View Interactor Presenter Entity Router(视图 交互 协调器 实体 路由)。VIPER 在责任划分层面进行了迭代,VIPER 分为五个层次:程序员
VIPER的特点就是职责明确,粒度细,隔离关系明确,这样能带来不少优势:github
VIPER由于需求的拆分粒度细,相应的会带来如下问题:服务器
VIPER框架最关键的是如何将相关接口定义出来,为了实现VIPER框架的目录结构,咱们将代码实现为以下目录结构: 网络
View中主要是当前页面的初始化等操做,并将页面事件传递给本身的Presenter架构
class MainTabView extends StatefulWidget implements BaseView {
const MainTabView({
Key key,
this.appBar,
this.views,
this.presenter,
});
final MainTabPresenter presenter;
// mainTab中的appBar使用
final PreferredSizeWidget appBar;
final List<TabModel> views;
@override
_MainTabViewState createState() => _MainTabViewState();
}
class _MainTabViewState extends State<MainTabView> with SingleTickerProviderStateMixin {
TabController tabController;
@override
void initState() {
super.initState();
tabController = new TabController(length: widget.views.length, vsync: this);
}
@override
void dispose() {
super.dispose();
tabController.dispose();
}
List<Tab> createTabs() {
List<Tab> tabs = new List<Tab>();
widget.views.forEach((e) {
var tab = Tab(
text: e.tabName,
icon: e.icon,
);
tabs.add(tab);
});
return tabs;
}
List<Widget> createBody() {
List<Widget> bodies = new List<Widget>();
widget.views.forEach((e) {
bodies.add(e.body);
});
return bodies;
}
@override
Widget build(BuildContext context) {
print(widget.views.map((e) => e.body));
return Scaffold(
backgroundColor: Colors.blue,
appBar: widget.appBar,
body: Material(
child: TabBarView(
controller: tabController,
children: createBody(),
),
),
bottomNavigationBar: SafeArea(
child: Material(
color: Colors.blue,
child: SafeArea(
child: TabBar(
onTap: (index) {
widget.presenter.tabChanged(index);
},
indicator: const BoxDecoration(),
controller: tabController,
tabs: createTabs(),
),
),
),
),
);
}
}
复制代码
Interactor中主要是实例化相关的数据,并将数据接口提供给Presenter以反馈给View使用:app
class MainTabViewModel {
List<TabModel> tabs;
MainTabViewModel({
this.tabs,
});
}
class MainTabInteractor implements BaseInteractor {
MainTabViewModel viewModel = MainTabViewModel(
tabs: [
TabModel(
tabName: '测试tab1',
body: Container(
child: Text('测试页面1'),
),
),
...
],
);
}
复制代码
Presenter主要是将Interactor中处理的viewModel反馈给View,并接收View中的页面事件,进行处理。
class MainTabPresenter implements BasePresenter {
@override
Widget create(List<TabModel> params) {
return MainTabView(
views: MainTabInteractor().viewModel.tabs,
presenter: this,
);
}
void tabChanged(int index) {
print('tab changed to: $index');
}
}
复制代码
Entity中主要是实现当前结构中所须要使用的各类类定义,并不须要作实体化操做
class TabModel implements BaseModel {
String tabName;
Icon icon;
Widget body;
TabModel({
this.tabName,
this.icon,
this.body,
});
}
复制代码
Router中主要定义push/pop操做时的一些动做,以及页面如何初始化。页面初始化均由Presenter触发。
class MainTabRouter extends BaseRouter {
@override
void push(context, params, title) {
super.push(context, params, title);
Route route = MaterialPageRoute(builder: (context) {
return MainTabPresenter().create(params);
});
Navigator.push(context, route);
}
}
复制代码
咱们在主路由中实现静态方法Push/Pop:
// 定义Router的key值,方便后续调用
enum RouterKey {
MainTab,
}
// 实现Router类
class Router {
static Map<RouterKey, BaseRouter> routeMap = {
RouterKey.MainTab: MainTabRouter(),
};
static void push(RouterKey destination, context, {params, title}) {
if (routeMap.containsKey(destination)) {
var router = routeMap[destination];
router.push(context, params, title);
}
}
static void pop(context) {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
}
}
复制代码
此时咱们的一套完整的VIPER流程就实现完成了
此时经过main中写入一个Button,用来触发Router的页面push效果:
body: Center(
child: MaterialButton(
onPressed: () {
Router.push("mainTab", context);
},
child: Text('push页面'),
),
),
复制代码
以后就能够看到完整的一套流程了:
1.增长页面建立脚本/插件,用于快速生成框架页面
2.抽离基类,以便于其余项目中使用