本文首发于 my blog

在业务中一般 MVVM 框架一般都会配合上数据状态库(redux, mobx 等)一起使用,本文会通过一个小 demo 来讲述为什么会引人数据状态库。

从 MVC 到 MVVM 模式说起

传统 MVC 架构(如 JSP)在当今移动端流量寸土寸金的年代一个比较头疼的问题就是会进行大量的全局重复渲染。但是 MVC 架构是好东西,其对数据、视图、逻辑有了清晰的分工,于是前端 MVC 框架(比如 backbone.js) 出来了,对于很多业务规模不大的场景,前端 MVC 框架已经够用了,它也能做到前后端分离开发单页面应用,那么它的缺陷在哪呢?

拿 backbone.js 说,它的 Model 对外暴露了 set 方法,也就是说可以在不止一个 View 里修改同个 Model 的数据,然后一个 Model 的数据同时对应多个 View 的呈现,如下图所示。当业务逻辑过多时,多个 Model 和多个 View 就会耦合到一块,可以想到排查 bug 的时候会比较痛苦。

针对传统 MVC 架构性能低(多次全局渲染)以及前端 MVC 框架耦合度高(Model 和 View) 的痛处,MVVM 框架完美地解决了以上两点。可以参阅之前写的 MVVM 框架解析之双向绑定

only MVVM

假设有这么一个场景,在输入框中查询条件,点击查询,然后在列表中返回相应内容。如下图所示:

假设用 react 实现,思路大体是先调用查询接口,调用成功后将获取到的数据通过 setState 存进 list 中,列表显示部分代码如下:

const Decorate = (ListComponent) => class extends Component {
constructor() {
super()
this.state = { list: [] }
} componentDidMount() {
fetch('./list.json')
.then((res) => res.json())
.then(result => this.setState({ list: result.data }))
} render() {
return (
<ListComponent data={this.state.list} />
)
}
}

接着往封装的 Decorate 组件里,传入无状态函数构建的 List 组件用来展示列表数据,代码如下:

function List(props) {
return (
<div>
{props.data.map(r =>
<p key={r.id}>{r.content}</p>
)}
</div>
)
}

可以看到 List 组件相当于是 View 层,而封装的 Decorate 组件相当于是 Model 层。但是这么做还是把业务逻辑写进了组件当中。而我们期望的是能得到一个纯粹的 Model 层和 View 层。接着一起看看 Flux 架构模式是如何解决这个问题的。

引人 Flux 架构模式

Flux 架构模式的 4 个重要组成部分以及它们的关系如上图所示,下文会根据 dispatch,store, action, view 的顺序逐步揭开 Flux 架构模式的面纱。

Flux 的源码中可以看出 Dispacher.js 是其的核心文件,其核心是基于事件的发布/订阅模式完成的,核心源码如下:

class Dispatcher {
...
// 注册回调函数,
register(callback) {
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
} // 当调用 dispatch 的时候会调用 register 中注册的回调函数
dispatch(payload) {
this._startDispatching(payload);
for (var id in this._callbacks) {
this._invokeCallback(id);
}
}
}

回顾下之前的目的:让 Store 层变得纯粹。于是定义了一个变量 comments 用来专门存放列表数据,在了解 Dispatcher 的核心原理之后,当调用 dispatch(obj) 方法时,就可以把参数传递到事先注册的 register 函数中,代码如下:

// commentStore.js
let comments = []
const CommentStore = {
getComment() {
return comments
}
} dispathcer.register((action) => { // 调用 Dispatcher 实例上的 register 函数
switch (action.type) {
case 'GET_LIST_SUCCESS': {
comments = action.comment
}
}
})

以及 action 中的函数如下:

// commentAction.js
const commentAction = {
getList() {
fetch('./list.json')
.then((res) => res.json())
.then(result =>
dispathcer.dispatch({ // 调用 Dispatcher 实例上的 dispatch 函数
type: 'GET_LIST_SUCCESS',
comment: result.data
}))
}
}

但是似乎少了点什么,当 GET_LIST_SUCCESS 成功后,发现还缺少通知到页面再次调用 CommentStore.getComment() 的能力,所以再次引用事件发布/订阅模式,这次使用了 Node.js 提供的 events 模块,对 commentStore.js 文件进行修改,修改后代码如下:

let comments = []
const CommentStore = Object.assign({}, EventEmitter.prototype, {
getComment() {
return comments
}, emitChange() {
this.emit('change')
}, addListener(callback) { // 提供给页面组件使用
this.on('change', callback)
}
}) appDispathcer.register((action) => {
switch (action.type) {
case 'GET_LIST_SUCCESS': {
comments = action.comment
CommentStore.emitChange() // 有了这行代码,也就有了通知页面再次进行调用 CommentStore.getComment 的能力
}
}
})

剩下最后一步了,就是整合 store 和 action 进页面中,代码如下:

class ComponentList extends Component {
constructor() {
super()
this.state = {
comment: commentStore.getComment()
}
} componentDidMount() {
commentStore.addListener(() => this.setState({ // 注册函数,上面已经提过,供 store 使用
comment: commentStore.getComment()
}))
} render() {
return (
<div>
{this.state.comment.map(r =>
<p key={r.id}>{r.content}</p>
)}
</div>
)
}
}

小结

单纯以 mvvm 构建应用会发现业务逻辑以及数据都耦合在组件之中,引入了 Flux 架构模式后数据和业务逻辑得到较好的分离。但是使用 Flux 有什么缺点呢?在下篇 《聊聊 Redux 架构模式》中会进行分析,下回见。

本文实践案例已上传至 stateManage

系列博客,欢迎 Star

探索从 MVC 到 MVVM + Flux 架构模式的转变的更多相关文章

  1. Android App的设计架构:MVC,MVP,MVVM与架构经验谈

    相关:http://www.cnblogs.com/wytiger/p/5996876.html 和MVC框架模式一样,Model模型处理数据代码不变在Android的App开发中,很多人经常会头疼于 ...

  2. Android App的设计架构:MVC,MVP,MVVM与架构AAAAA

    1. 架构设计的目的1.1 通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合.1.2 这样做的好处是使得程序在开发的过程中,开发人员只需要专注于一点,提高程序开发的效率,并且更容易进行后续 ...

  3. iOS学习之MVC,MVVM,MVP模式优缺点

    为什么要关注架构设计? 因为假如你不关心架构,那么总有一天,需要在同一个庞大的类中调试若干复杂的事情,你会发现在这样的条件下,根本不可能在这个类中快速的找到以及有效的修改任何bug.当然,把这样的一个 ...

  4. 简述 MVC, MVP, MVVM三种模式

    Make everything as simple as possible, but not simpler - Albert Einstein* 把每件事,做简单到极致,但又不过于简单 - 阿尔伯特 ...

  5. 架构模式:MVC与MVVM

    本文探讨如下几个问题: 什么是MVC 什么是MVVM MVC与MVVM对架构属性的影响 MVC实例SpringMVC MVVM实例Vue MVC.MVVM与Layer中的Model,Controlle ...

  6. Extjs6官方文档译文——应用架构简介(MVC,MVVM)

    应用架构简介 Extjs 同时提供对于MVC和MVVM应用架构的支持.这两个架构方式共享某些概念,而且都旨在沿着逻辑层面划分应用程序代码.每种方法在选择如何划分应用组件上都有其各自的优势. 本指南的目 ...

  7. Flux架构

    引言:本文的目标是通过了解Flux 提出的模式,来明白Flux 的核心要点,以及弄清楚它到底是什么.并且,由于Flux不是传统意义上的软件包,因此我们将仔细研究通过Flux 来解决设计思路上的问题. ...

  8. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  9. android MVC && MVP && MVVM分析和对比

    相关:http://www.cnblogs.com/wytiger/p/5305087.html 出处http://blog.csdn.net/self_study,对技术感兴趣的同鞋加群544645 ...

随机推荐

  1. Ajax 案例之三级联动

    每次在博客园网站写博客,格式真的好难搞,还望好心人告知更好的编辑工具.接下来进入正题:三级联动(其效果演示可看我的博文Ajax 学习总结 末尾). 数据表设计(Oracle) 新建数据表 Employ ...

  2. volatile 与 synchronized 区别

    在Java中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式: 同步 如用synchronized关键字,或者使用锁对象. volatile 使用volatile关键字用一句话概括vola ...

  3. 你必须知道的session与cookie

    Session本质 提到Session我们能联想到的就是用户登录功能,而本身我们使用Session的基础是通过url进行访问的,也就是使用http协议进行访问的,而http协议本身是无状态的,那么问题 ...

  4. [国嵌攻略][156][I2C自编设备驱动设计]

    AT24C08的驱动在Linux内核中已经提供,在/drivers/misc/eeprom/at24.c文件中.在对应的probe函数中有一个创建/sys/.../eeprom文件的函数,应用程序通过 ...

  5. [国嵌攻略][070-095][Linux编程函数手册]

    第1类 时间编程类 1.1 获取日历时间 1.1.1 函数名 time 1.1.2 函数原形 time_t time(time_t *t) 1.1.3 函数功能 返回日历时间 1.1.4 所属头文件 ...

  6. 番外篇--Moddule Zero多租户管理

    番外篇--Moddule Zero多租户管理 2.1.1 关于多租户 强烈建议阅读这个文件前阅读多租户文档. 2.1.2 启用多租户 ASP.NET Boilerplate和module-zero可以 ...

  7. Linux apache的运行用户和用户组

    我们在安装apache后,有时在上传文件的时候,提示没有权限或者是不可写,我们都会去查文件夹的权限. 通过ls -l /var/www/html/website可以很直观的看出我们文件和文件夹的权限, ...

  8. P2P视频模块

    P2P视频模块数据手册 公  司 : 深圳市万秀电子有限公司 网  站 : http://www.wanxiucx.com 总  机 : 0755-23215689 联系人: 张先生 手  机 : 1 ...

  9. 2017-07-12(touch df du)

    touch 作用:修改文件时间或者创建新文件 文件三个主要的变动时间 mtime 当文件的内容发生改变的时候,就会更新这个时间 ctime 当文件的权限与属性发生改变的时候,就会更新这个时间 atim ...

  10. python_如何通过twisted实现数据库异步插入?

    如何通过twisted实现数据库异步插入? 1. 导入adbapi 2. 生成数据库连接池 3. 执行数据数据库插入操作 4. 打印错误信息,并排错 #!/usr/bin/python3 __auth ...