Mvvm 前端数据流框架精讲

原文连接, 若是感兴趣能够加QQ群: 157937068, 一块儿交流。前端

本次分享是带你们了解什么是 mvvm,mvvm 的原理,以及近几年产生了哪些演变。react

同时借 mvvm 这个话题拓展到对各种前端数据流方案的思考,造成对前端数据流总体认知,帮助你们在团队中更好的作技术选型。编程

Mvvm 的概念与发展

Mvvm & 单向数据流

Mvvm 是指双向数据流,即 View-Model 之间的双向通讯,由 ViewModel 做桥接。以下图所示:redux

image

而单向数据流则去除了 View -> Model 这一步,须要由用户手动绑定。swift

生态 - 内置 & 解耦

许多前端框架都内置了 Mvvm 功能,好比 Knockout、Angular、Ember、Avalon、Vue、San 等等。前端框架

而就像 Redux 同样,Mvvm 框架中也出现了许多与框架解耦的库,好比 Mobx、Immer、Dob 等,这些库须要一个中间层与框架衔接,好比 mobx-react、redux-box、dob-react。解耦让框架更专一 View 层,实现了库与框架灵活搭配的能力。数据结构

解耦的数据流框架也诠释了更高抽象级别的 Mvvm 架构,即:View - 前端框架,Model - (mobx, dob),ViewModel - (mobx-react, dob-react)。架构

同时也实现了数据与框架分离,便于测试与维护。好比下面的例子,左边是框架无关的纯数据/数据操做定义,右边是 View + ViewModel:app

image

运行效率 - 脏检测 & getter/setter 劫持

Angluar 早期的脏检测机制虽然开创了 mvvm 先河,但监听效率比较低,须要 N + 1 次确认数据是否有联动变化,就像下图所示:框架

image

如今几乎全部框架都改成 getter/setter 劫持实现监听,任何数据的变化均可以在一个事件循环周期内完成:

image

语法 - 特殊语法 & 原生语法

早期一些 Mvvm 框架须要手动触发视图刷新,如今这种作法几乎都被原生赋值语句取代。

数据变动方式 - Mutable & Immutable

下图的代码语法虽为 mutable,但产生的结果多是 mutable,也多是 immutable,取决于 mvvm 框架内置实现机制:

image

Connect 的两种写法

因为 mvvm 支持了 mutable 与 immutable 两种写法,因此对于 mutable 的底层,咱们使用左图的 connect 语法,对于 immutable 的底层,须要使用右图的 conenct 语法:

[图片上传失败...(image-b7408b-1522595335875)]

对左图而言,因为 mutable 驱动,全部数据改动会自动调用视图刷新,所以不但更新能够一步到位,并且能够数据全量注入,由于没用到的变量不会致使额外渲染。

对右图,因为 immutable 驱动,自己并无主动驱动视图刷新能力,因此当右下角节点变动时,会在整条链路产生新的对象,经过 view 更新机制一层层传导到要更新的视图。

从 TFRP 到 mvvm

讲到 mvvm 的原理,先从 TFRP 提及,详细能够参考 dob-框架实现 这里以 dob 框架为例子,一步步介绍了如何实现 mvvm。本文简单作个介绍。

autorun & reaction

autorun 是 TFRP 的函数效果,即集成了依赖收集与监听,autorun 背后由 reaction 实现。

image

reaction 实现 autorun

以下图所示,autorun 是 subscription 套上 track 的 reaction,而且初始化时主动 dispatch,从入口(subscription)处激活循环,完成 subscription -> track -> 监听修改 -> subscription 完成闭环。

image

track 的实现

每一个 track 在其执行期间会监听 callback 的 getter 事件,并将 target 与 properityKey 存储在二维 Map 中,当任何 getter 触发后,从这个二维表中查询依赖关系,便可找到对应的 callback 并执行。

image

View-Model 的实现

因为 autorun 与 view 的 render 函数很像,咱们在 render 函数初始化执行时,使其包裹在 autorun 环境中,第 2 次 render 开始遍剥离外层的 autorun,保证只绑定一遍数据。

这样 view 层在本来 props 更新机制的基础上,增长了 autorun 的功能,实现修改任何数据自动更新对应 view 的效果。

image

Mvvm 的缺点与解法?

Mvvm 全部已知缺点几乎都有了解决方案。

没法监听新增属性

用过 Mobx 的同窗都知道,给 store 添加一个不存在的属性,须要使用 extendObservable 这个方法。这个问题在 Dob 与 Mobx4.0 中都获得了解决,解决方法就是使用 proxy 替代 Object.defineProperty

image

异步问题

因为 getter/setter 没法得到当前执行函数,只能经过全局变量方式解决,所以 autorun 的 callback 函数不支持异步:

image

嵌套问题

因为 reaction 特性,只支持同步 callback 函数,所以 autorun 发生嵌套时,极可能会打乱依赖绑定的顺序。解决方案是将嵌套的 autorun 放到执行队列尾部,以下图所示:

image

无数据快照

mutable 最被人诟病的一点就是没法作数据快照,不能像 redux 同样作时间回溯。有问题天然有人会解决,Mobx 做者的 Immer 库完美的解决了问题。

image

原理是经过 proxy 返回代理对象,在内部经过浅拷贝替代对对象的 mutable 更改。具体原理能够参考个人这篇文章:精读 Immer.js 源码

image

无反作用隔离

mvvm 函数的 Action 因为支持异步,许多人会在 Action 中发请求,同时修改 store,这样就没法将请求反作用隔离到 store 以外。同时对 store 的 mutable 修改,自己也是一种反作用。

image

虽然能够将请求函数拆分到另外一个 Action 中,但人为因素没法彻底避免。

自从有了 Immer.js 以后,至少从支持元编程的角度来看,mutable 并不必定会产生反作用,它能够是零反作用的:

function inc(obj) {
  return produce(obj => obj.count++)
}
复制代码

上面这种看似 mutable 的写法实际上是零反作用的纯函数,和下面写法等价:

function inc(obj) {
  return {
    count: obj.count + 1,
    ...obj
  }
}
复制代码

而对反作用的隔离,也能够作出相似 dva 的封装:

image

Mvvm store 组织形式

Mvvm 在项目中 stores 代码结构也变幻无穷,这里列出 4 种常见形式。

对象形式,表明框架 – mobx

mobx 开创了最基本的 mvvm store 组织形式,基本也是各内置 mvvm 框架的 store 组织形式。

image

Class + 注入,表明框架 – dob

dob 在 store 组织形式下了很多功夫,经过依赖注入加强了 store 之间的关联,实现 stores -> action 多对一的网状结构。

image

数据结构化,表明框架 – mobx-state-tree

mobx-state-tree 是典型结构化 store 组织的表明,这种组织形式适合一体化 app 开发,好比不少页面之间细粒度数据须要联动。

image

约定与集成,表明框架 – 类 dva

类 dva 是一种集成模式,是针对 redux 复杂的样板代码,思考造成的简化方案,天然集成与约定是简化的方向。

另外这种方案更像一层数据 dsl,得益于此,同一套代码能够拥有不一样的底层实现。

image

Mvvm vs Reactive programming

Mvvm 与 Reactive programming 都拥有 observable 特性,经过下面两张图能够轻松区分:

image

上面红线是 mvvm 的 observable 部分,这里指的是数据变化的 autorun 动做。

image

上面红线是 Reactive programming 的 observable 部分,指的是数据源派发流的过程。

Mvvm 与 Reactive programming 的结合

既然 redux 能够与 rxjs 结合(redux-observable),那么 mvvm 应该也能够如此。

下面是这种方案的构想:

image

rxjs 仅用来隔离反作用与数据处理,mvvm 拥有修改 store 的能力,而且精准更新使用的 View。

总结

根据业务场景指定数据流方案,数据流方案没有银弹,只有贴着场景走,才能找到最合适的方案。

了解到 mvvm 的发展与演进,让不一样数据流方案组合,你会发现,数据流方案还有不少。

相关文章
相关标签/搜索