前端框架系列之(mvvm)

簡介

前面我們介紹過了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的文章:

業務需求

  1. 接收用戶輸入的“用戶名”和“密碼”做登錄操作
  2. 登錄成功後返回“登錄成功提示”

項目搭建

我們直接用上一節的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章