MVVM与Redux的概念类比

先说MVC

在这里插入图片描述
在这里插入图片描述

  • 注意这里的箭头是单向的,单向的,单向的。

  • 优点:只具有单向的操作流,数据驱动。逻辑清晰。

  • 缺点:view监听Model较多,不可重用。controller不易于测试。

  • controller是MVC中的数据和视图的协调者,也就是在Controller里面把Model的数据赋值给View来显示(或者是View接收用户输入的数据然后由Controller把这些数据传给Model来保存到本地或者上传到服务器)

MVP

在这里插入图片描述

  • 所有操作都通过Presenter,Presenter,Presenter。

优点:易于直接通过Presenter测试。view可重用。

缺点:presenter不仅有业务逻辑,还要手动同步View和model,笨重。

再说MVVM

在这里插入图片描述

  • Model隐式更新View是通过Binder,Binder,Binder。

  • 优点:逻辑简化。可双向绑定。

  • 缺点:双向绑定可能会循环调用。依赖Binder质量。

  • MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。

  • Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

  • Model: 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。我们可以把Model称为数据层,因为它仅仅关注数据本身,不关心任何行为

  • View: 用户操作界面。当ViewModel对Model进行更新的时候,会通过数据绑定更新到View

  • ViewModel: 业务逻辑层,View需要什么数据,ViewModel要提供这个数据;View有某些操作,ViewModel就要响应这些操作,所以可以说它是Model for View.

总结: MVVM模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。

React

发明了GraphQL专门用于高效提供数据,发明React实现对视图的组件化开发,启用第三方的Redux,处理组件间与前后端的通信。GraphQL,React与Redux分别对应MVVM的M,V,VM。

React的更新是自顶向下的,为了减少不要必要的更新,内部提供了两个门闸,一个是只接受props的情况下, 通常会引发componentWillReceiveProps钩子,如果props, state( React16的getDerivedStateFromProps也发引发state改变 )及context 都没有改变, 这时不会更新子组件。另一种是setState的情况,由著名的shouldComponentUpdate来阻断更新。 React 50% 的性能优化手段都集中在shouldComponentUpdate的改良上,其中immutable机制必不可少,它只比较引用,不进行深比较。之前也有PureComponent,只比较对象的第一层属性差异。

immutable机制可以通过immutable.js提供,也可以通过redux来提供。 redux的官网一开始罗罗索索说了这么多,就是讲如何构建一个store, store的每个属性是如何更新,一更新则直接修改它的上方所有容器,这种修改模型与immutable.js如出一辙。针对如何修改一个属性,于是搞出action type的概念,为了简化action创建的又发明了createAction, 怎么修改则引出了reducer的概念,然后触发修改又搞了一个dispatch方法。reducer是一个纯函数,有时我们需要一点点副作用,于是又提供了middleware。怎么就这么复杂呢!

最大的原因还是为了immutable(不可变),于是需要纯函数。纯函数封装着一个不能被随意改修的数据,要访问它只能通过getState方法。于是我们能联想到,reducer纯函数不就是MVVM中的setter吗?! getState不就是getter吗?!
在这里插入图片描述
MVVM之所以轻松,是因为它将我们的模型对象全部进行遍历,一层层的遍历,将大部分属性转换为访问器属性。什么是访问器属性,就是用Object.defineProperty创建的属性,里面有setter, getter,多了两个方法,我们就有许多施展魔法的空间。setter用来更新视图,getter用来收集依赖。因为VM或叫store的属性大都会放到视图中,视图的{}或{{}}`这些位置上,这些位置可以换成视图刷新函数,因此 视图刷新函数要更新视图时,肯定会调用getter方法,于是这时就能确认这个方法与这个属性的关联关系,一个属性与多个视图刷新函数有关联,一对多,这正是观察者模式的用武之地。当我们再次改变这属性时,就可以遍历存放视图刷新函数所在的数组,实现视图的最小化刷新。

redux 的问题在于,这个setter实现得太痛苦了。修改一个属性有不同的方式,于是有多个reducer, reducer写了一大堆分支,官方很nice教你用 switch,但还是很冗长。最后出现了redux-box, rematch这样的库,直接精简掉action的概念, 一个state就是对应一个model(想一下ng-model, v-model), model有原始值,有同步操作它的方法集(reducers),还有基于其上的异步操作方法集。

export const count = {
  state: 0, // 它的初始值
  //reducers与effects都是各种操作count的方法,但是我们不应该有action概念,省得自己被绕进去
  reducers: {
    //reducers是一种纯正的setter,由于immutable的需求,要求传入旧的state, 返回新的state
    increment(state, payload) {
      return state + payload
    }
  },
  // 
  effects: (dispatch) => ({
    // effects是一种`异步`的setter,dispatch为访问器属性count所在的vm, 之所以单独出来,因为async/await需要特殊处理
    async incrementAsync(payload, rootState) {
      await new Promise(resolve => setTimeout(resolve, 1000))
      dispatch.count.increment(payload)
    })
  }
}

想获取这个model的数据,则直接通过 store.getState()。

因此这时我们就可以绕开action,绕开了action也就不需要dispatch方法。 rematch通过一个dispatch对象,以经典的点点风格,就能找到要更新model,然后再在上面找到对应的setter

import store from './index'
const { dispatch } = store

dispatch.count.increment(1)                       // state = { count: 2 }
dispatch.count.incrementAsync(1)

当一个属性发生改变时,我们要让程序做些什么,那么redux提供了一个subscribe方法。这东西不就是avalon/angular/vue的watch方法吗!

当任何一个属性发生改变,我们要让程序做些什么,redux有一个middlewares机制,类似于koa的洋葱结构。这个在avalon有watch("*", fn), angular可以$watch一个对象,这样所有属性的变化都经会触发这回调。

redux还有selector的概念,这个东西类似MVVM中的计算属性,它不存在于后端模型中,需要由已经有的属性组合出来。

function mapStateToProps(state, props) {
return {//author是动态计算出来的selector
    author: state.authors[props.authorId],
  };
}

因此如何能精简掉action的概念,简化调用流程,redux就能轻快起来。于是必须封装,于是有redux-saga, dva, rematch这样的东西。这些框架里面,个人是强烈建议使用rematch,没有太多魔术,处理异步是使用更易上手的async/await。anujs里面就使用一个改装版rematch做它的store。
链接

在这里插入图片描述
翻译于 https://zhuanlan.zhihu.com/p/38025611

posted @ 2020-11-09 16:44  嗨Sirius  阅读(130)  评论(0编辑  收藏  举报