怎樣完成一個MV*形式(MVC/MVP/MVVM)

如果讓你不依託任何前端框架(React/Vue/Angular等等),純真用Javascript編寫一個網站運用,你還知道怎樣開闢嗎?

舉個例子,產物司理讓你完成一個網頁,上面有一張貓咪的圖片,貓咪的下面顯現點贊的次數。每次點擊貓咪的圖片,點贊的数字加一。

這個對人人來講應當都很簡樸。

這時刻產物司理最先加需求了,網頁上展現五張貓咪圖片,離別有本身的點贊次數,點擊貓咪圖片,相對應的點贊次數加一。這時刻你想怎樣改寫本身的順序呢?你的順序如今看起來是不是邏輯清楚,構造清楚,可拓展性強呢?

本日我就要帶人人用MV形式來構造代碼,編寫出高質量幽美的前端項目。起首我們要也許搞清楚一些什麼MV形式。

什麼MV*形式

MV*是MVC/MVP/MVVM等的一個統稱,它們各有不同,但本質上實際上是一個東西。MVP和MVVM是MVC的變體。所以我們本日不議論它們的區分,只關注中心的東西。

M代表的是Model,用於封裝與運用順序的營業邏輯相干的數據以及對數據的處置懲罰要領。Model有對數據直接接見的權利,比方對數據庫的接見。Model 不體貼它會被怎樣顯現或是怎樣被操縱。

V代表的是View,用於將數據有目標的顯現出來,在 View 中平常沒有順序上的邏輯。

末了的*,不管是Controller照樣Presenter,照樣ViewModel,本質上做的事變就是銜接M和V,搭建M和V溝通的橋樑。讓M和V不直接溝通,到達職責星散的結果。

我們能夠看維基百科上一個極簡的MVC完成:

/** 模仿 Model, View, Controller */
var M = {}, V = {}, C = {};

/** Model 擔任寄存材料 */
M.data = "hello world";

/** View 擔任將材料顯現到屏幕上 */
V.render = (m) => { alert(m.data); }

/** Controller 作為一個 M 和 V 的橋樑 */
C.handleOnload = () => { V.render(M); }

/** 在網頁讀取的時刻挪用 Controller */
window.onload = C.handleOnload;

我們本日要完成的MV*就要滿足這幾個前提:

  1. Model保留我們的數據
  2. View擔任襯着節點,能夠有多個View
  3. *(我們給它取個名字叫Bridge)為View供應讀取和修正Model的要領

產物司理的需求

最終版:網頁左邊展現一個可挑選的貓咪名字列表,右邊展現當前選中的貓咪概況,包含貓咪稱號,貓咪圖片,該貓咪被點贊數目和一個點贊按鈕。點擊點贊按鈕,當前貓咪的點贊數目加1。結果圖以下,我們只體貼功用完成,所以款式丑我們先忍一下。

《怎樣完成一個MV*形式(MVC/MVP/MVVM)》

HTML && CSS

<div id="main">
  <ul id="cat-list"></ul>

  <div id="cat-container">
    <h1 id="cat-title"></h1>
    <img id="cat-img" src="" alt="placekitten">
    <div class="cat-likes">
      <span id="likes-count"></span>
      <span id="likes-btn">👏</span>
    </div>
  </div>
</div>

構造很清楚,主要有一個id是cat-list的貓咪列表和一個id是cat-container的貓咪概況,貓咪概況包含三部份,貓咪名字,貓咪圖片和點贊區。增加一點簡樸的款式。

#main {
  display: flex;
}

#cat-list {
  flex: 0 0 100px;
}

#cat-list li {
  cursor: pointer;
}

#cat-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}

#cat-img {
  width: 300px;
  height: 300px;
}

#likes-btn {
  cursor: pointer;
}

Model

let model = {
    currentCat: null,
  cats: [
      {
        title: '我是一號喵喵',
      imageSrc: 'https://placekitten.com/300/300',
      likesCount: 0
    },
    {
        title: '我是二號喵喵',
      imageSrc: 'https://placekitten.com/301/301',
      likesCount: 0
    },
      {
        title: '我是三號喵喵',
      imageSrc: 'https://placekitten.com/302/302',
      likesCount: 0
    },
      {
        title: '我是四號喵喵',
      imageSrc: 'https://placekitten.com/303/303',
      likesCount: 0
    }
  ]
};

我們的數據包含兩部份,currentCat示意當前展現的貓咪概況對象, cats示意貓咪列表。能夠看出我們如今的Model就是純真的數據展現,沒有任何與展現相干的邏輯,將來拓展起來異常輕易。

Bridge

方才說了Bridge要擔任為View供應一切對Model數據的讀取和修正的要領。所以我們能夠思索下,有哪些要領須要供應。

  1. 起首要供應一個Init的要領,實行初次襯着view的事情
  2. 獵取當前挑選的貓咪概況
  3. 才列表挑選要閱讀的貓咪
  4. 獵取貓咪列表
  5. 為當前挑選的貓咪點贊
let brain = {
    init: () => {
        model.currentCat = model.cats[0]; //初始化
    
        catListView.init(); //這兩個View稍後供應
        catView.init();
    },

    getCurrentCat: () => model.currentCat,
  
    setCurrentCat: (cat) => {
        model.currentCat = cat;
      catView.render();   // 手動觸發View從新襯着,這是將來我們的主要優化點
    },
  
    getCats: () => model.cats,
  
     incrementLikes: () => {
        model.currentCat.likesCount++;
      catView.render();   // 手動觸發View從新襯着,這是將來我們的主要優化點
    }
};

View

我們有兩個View,一個是貓咪列表catListView, 另一個是貓咪概況catView。

let catView = {
    init: function () {
        this.catTitle = document.getElementById('cat-title');
        this.catImg = document.getElementById('cat-img');
        this.likesCount = document.getElementById('likes-count');
        this.likesBtn = document.getElementById('likes-btn');
    
        this.likesBtn.addEventListener('click', () => {
            brain.incrementLikes();
        })
    
        this.render()
  },
  
  render: function () {
      let currentCat = brain.getCurrentCat();
      this.catTitle.textContent = currentCat.title;
      this.catImg.src = currentCat.imageSrc;
      this.likesCount.textContent = currentCat.likesCount;
  }
}

注意到,我們把init和render拆成兩個要領。

init要領起首把相干的DOM節點先保留下來,防止後續每次還得從新尋覓DOM節點;然後為點贊按鈕綁定點擊事宜,點擊時挪用brain供應的incrementLikes要領修正Model;末了實行render完成初次襯着。

而render函數儘管襯着,每次挪用都邑直接修正DOM節點,更新襯着。

let catListView = {
    init: function () {
        this.catListElem = document.getElementById('cat-list');
        this.render();
    },
  
    render: function () {
        let cats = brain.getCats();
    
        cats.forEach(cat => {
            let catElem = document.createElement('li');
            catElem.textContent = cat.title;
      
            catElem.addEventListener('click', () => {
                  brain.setCurrentCat(cat);
              })
      
            this.catListElem.appendChild(catElem)
        })
    }
}

catListView構造與上面相似。

設置完model, brain, catlistview, catview四個對象后,我們在末了挪用brain.init()就完成了一切事情。

末了

本日我們就完成了一個MV形式,它基本上歸納綜合了MV形式的中心。然則遺留了一個很主要的題目:每當我們數據修正的時刻,我們都手動挪用了一下view的render函數來從新襯着頁面,如許顯然是不智慧的。下期我們細緻研究一下MVVM的代表Backbone, knockout等等以及Vue是怎樣完成他們的MV*。

    原文作者:zach5078
    原文地址: https://segmentfault.com/a/1190000014725342
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞