簡介
前面我們介紹過了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