简介
前面我们介绍过了mvc 前端框架系列之(mvc),mvp 前端框架系列之(mvp),MVP中我们说过随着业务逻辑的增加,UI的改变多的情况下,会有非常多的跟UI相关的case,这样就会造成View的接口会很庞大。而MVVM就解决了这个问题,通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行。
- Model(模型)表示应用程序核心(比如数据库记录列表)。
- View(视图)显示数据(数据库记录)。
- ViewModel (视图模型) 暴露公共属性和命令的视图的抽象。
我们再看一下mvc的设计图:
在这里插入图片描述再看一下mvp的设计图:
在这里插入图片描述最后看一下mvvm的设计图:
在这里插入图片描述有没有发现,不管是mvc、mvp、mvvm其实都差不多呢?其实区别还是挺大的,在MVP中View和presenter要相互持有,方便调用对方,而在MVP中 View和ViewModel通过Binding进行关联,他们之前的关联处理通过绑定器(DataBinding)完成。
其实vue本身就是一个mvvm架构,通过es5的Object.defineProperty监听数据的变化,然后通过观察者模式通知各个vue实例,最后触发dom的更新,实现了数据驱动视图。感兴趣的可以去看一下我之前写的两篇关于vue的文章:
业务需求
- 接收用户输入的“用户名”和“密码”做登录操作
- 登录成功后返回“登录成功提示”
项目搭建
我们直接用上一节的demo
https://github.com/913453448/vue-property-decorator-demo
我们copy一个mvc目录为mvvm:
在这里插入图片描述同样,我们修改一下main.ts的入口为mvvm/index.vue
main.ts:
import Vue from "vue";
import Demo from "./mvvm/index.vue";
new Vue({
render(h) {
return h(Demo);
}
}).$mount("#app");
Model
model实现没变,还是跟mvc一样。
UserModelImp.ts
import User from "./User";
/**
* user数据持久化接口层
*/
export default interface IUserModel {
/**
* 用户登录
* @param {string} name
* @param {string} pwd
* @returns {Promise<User>}
*/
login(name: string, pwd: string): Promise<User>
}
UserModelImp.ts
import User from "./User";
import IUserModel from "./IUserModel";
/**
* user数据持久化实现层
*/
export default class UserModelImp implements IUserModel {
/**
* 用户登录
* @param {string} name
* @param {string} pwd
* @returns {Promise<User>}
*/
login(name: string, pwd: string): Promise<User> {
return new Promise((resolve, reject) => {
if ("123456" === pwd) {
const user = new User();
user.id = "1000";
user.name = name;
user.pwd = pwd;
resolve(user);
} else {
reject(new Error("密码错误"));
}
});
}
};
ViewModel
vue中已经为我们提供了一个叫vuex的工具,vuex完美的替代了我们的整个ViewModel,包括数据绑定器(databinding)。
首先我们安装vuex:
yarn add vuex || npm install -S vuex
我们在mvvm目录底下创建一个叫store的文件
store.ts
import Vue from 'vue';
import Vuex, {StoreOptions} from 'vuex';
import User from "../User";
import UserModelImp from "../UserModelImp";
Vue.use(Vuex);
//创建一个用户业务处理实例
const userModelImp = new UserModelImp();
const store = new Vuex.Store({
state: {
id: "",
name: "",
pwd: "",
},
mutations: {
updateUser(state, user: User) {
state.id = user.id;
}
},
actions: {
/**
* 登录
* @param context
* @returns {Promise<Error | never | User>}
*/
login(context) {
return userModelImp.login(this.state.name, this.state.pwd)
.then((user) => {
context.commit("updateUser", user);
return "欢迎你," + user.name;
}).catch((error) => {
return new Error("登录失败: " + error.message);
});
}
}
} as StoreOptions<User>);
export default store;
我们直接创建了一个vuex中的store,vuex代表了我们的ViewModel,那么ViewModel是怎么跟view连接起来的呢?通过vuex的store跟页面进行数据绑定的,实现上面的代码想必大家问题都不大,不懂的童鞋可以去看vuex的官网,感兴趣的还可以看一下我之前写的几篇关于vuex的文章:
index.ts
把store当viewmodel暴露出去
import store from "./store";
export {
store
};
View
IUserView.ts接口跟之前mvc、mvp一样
IUserView.ts
/**
* user view接口
*/
export default interface IUserView {
/**
* 登录响应
*/
onLogin(): void;
/**
* 展示消息
* @param {string} msg
*/
showMessage(msg: string): void;
}
index.vue我们直接通过vuex暴露在vue原型上的$store属性建立跟viewmodel的关联
index.vue
<template>
<div>
用户名:<input name="name" v-model="$store.state.name"><br>
密码:<input name="pwd" v-model="$store.state.pwd"><br>
<button @click="onLogin">登录</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "../view/component";
import IUserView from "./IUserView";
@Component
class UserViewImp extends Vue implements IUserView {
/**
* 去登录
*/
onLogin() {
this.$store.dispatch('login').then((msg:string)=>{
this.showMessage(msg);
}).catch((error:Error)=>{
this.showMessage(error.message);
});
}
/**
* 展示消息
* @param {string} msg
*/
showMessage(msg = "") {
alert(msg);
}
}
export default UserViewImp;
</script>
可以看到,我们view中直接通过vuex的store建立了跟viewmodel的关系,然后通过vuex的store的dispatch方法触发viewmodel的action,viewmodel再通过usermodel获取数据,最后viewmodel直接修改state数据触发视图更新,其实小伙伴可以直接把vuex看成我们mvvm的viewmodel。
这里特别声明一下
小伙伴有没有看到我们代码中:
<div>
用户名:<input name="name" v-model="$store.state.name"><br>
密码:<input name="pwd" v-model="$store.state.pwd"><br>
<button @click="onLogin">登录</button>
</div>
我们直接通过v-model绑定store的state对象的属性了,这样做其实是非常危险的,我们这里为了偷懒才这样写的,正常项目的话小伙伴需要通过computed关联state.name供当前组件使用,要修改state.name的话需要通过监听input的输入,然后触发vuex的action,最后通过mutation触发state.name的改变,也就是在vuex中只有mutation有权利修改state的值,为什么这样做呢? 主要就是为了更好的跟踪state的变化。
上面说的都是vuex官网中的规范,小伙伴可以自行去查看vuex的官网。
编译运行
npm run dev
运行结果
在这里插入图片描述在这里插入图片描述
总结
- 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变
- 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑(这里说的是可以像mvp的presenter)
- 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
-
可测试。测试可以利用数据驱动视图了,可以针对viewmodel的开发
代码
我们已经说了mvc、mvp、mvvm,那我们到底需要怎么去选择呢?
其实框架这种东西吧,没有绝对的框架,用起来大家都能接受、代码看起来很爽、维护起来方便、别再被人骂像💩一样就就可以了,管它啥啥啥呢!!!
源代码已经上传到github了
https://github.com/913453448/vue-property-decorator-demo.git
网友评论