一個只有十行的精簡MVVM框架(上篇)


本文來自網易雲社區前端

前言


MVVM模式相信作前端的人都不陌生,去網上搜MVVM,會出現一大堆關於MVVM模式的博文,可是這些博文大多都只是用圖片和文字來進行抽象的概念講解,對於剛接觸MVVM模式的新手來講,這些概念雖然可以讀懂,可是也很難作到理解透徹。所以,我寫了這篇文章。vue


這篇文章旨在經過代碼的形式讓你們更好的理解MVVM模式,相信大多數人讀了這篇文章以後再去看其餘諸如regular、vue等基於MVVM模式框架的源碼,會容易不少。web


若是你對MVVM模式已經很熟悉而且也已經研讀過並深入理解了當下主流的前端框架,能夠忽略下面的內容。若是你沒有一點JavaScript基礎,也請先去學習下再來閱讀讀此文。前端框架



引子


來張圖來鎮壓此文:數據結構


0.jpg



MVVMModel-View-ViewModel的縮寫。簡單的講,它將ViewModel層分隔開,利用ViewModel層將Model層的數據通過必定的處理變成適用於View層的數據結構並傳送到View層渲染界面,同時View層的視圖更新也會告知ViewModel層,而後ViewModel層再更新Model層的數據。app


咱們用一段學生信息的代碼做爲引子,而後一步步再重構成MVVM模式的樣子。框架


編寫相似下面結構的學生信息:dom

  • Name: Jessica Bremvvm

  • Height: 1.8m函數

  • Weight: 70kg

用常規的js代碼是這樣的:


const student = {
    'first-name': 'Jessica',
    'last-name': 'Bre',
    'height': 180,
    'weight': 70,
}
const root = document.createElement('ul')
const nameLi = document.createElement('li')
const nameLabel = document.createElement('span')
nameLabel.textContent = 'Name: '
const name_ = document.createElement('span')
name_.textContent = student['first-name'] + ' ' + student['last-name']
nameLi.appendChild(nameLabel)
nameLi.appendChild(name_)
const heightLi = document.createElement('li')
const heightLabel = document.createElement('span')
heightLabel.textContent = 'Height: '
const height = document.createElement('span')
height.textContent = '' + student['height'] / 100 + 'm'
heightLi.appendChild(heightLabel)
heightLi.appendChild(height)
const weightLi = document.createElement('li')
const weightLabel = document.createElement('span')
weightLabel.textContent = 'Weight: '
const weight = document.createElement('span')
weight.textContent = '' + student['weight'] + 'kg'
weightLi.appendChild(weightLabel)
weightLi.appendChild(weight)
root.appendChild(nameLi)
root.appendChild(heightLi)
root.appendChild(weightLi)
document.body.appendChild(root)


好長的一堆代碼呀!別急,下面咱們一步步優化!


DRY一下如何


程序設計中最普遍接受的規則之一就是「DRY」: "Do not Repeat Yourself"。很顯然,上面的一段代碼有不少重複的部分,不只與這個準則相違背,並且給人一種不舒服的感受。是時候作下處理,來讓這段學生信息更"Drier"。


能夠發現,代碼裏寫了不少遍document.createElement來建立節點,可是因爲列表項都是類似的結構,因此咱們沒有必要一遍一遍的寫。所以,進行以下封裝:


const createListItem = function (label, content) {
    const li = document.createElement('li')
    const labelSpan = document.createElement('span')
    labelSpan.textContent = label
    const contentSpan = document.createElement('span')
    contentSpan.textContent = content
    li.appendChild(labelSpan)
    li.appendChild(contentSpan)
    return li
}


通過這步轉化以後,整個學生信息應用就變成了這樣:


const student = {
    'first-name': 'Jessica',
    'last-name': 'Bre',
    'height': 180,
    'weight': 70,
}

const createListItem = function (label, content) {
  const li = document.createElement('li')
  const labelSpan = document.createElement('span')
  labelSpan.textContent = label
  const contentSpan = document.createElement('span')
  contentSpan.textContent = content
  li.appendChild(labelSpan)
  li.appendChild(contentSpan)
  return li
}
const root = document.createElement('ul')
const nameLi = createListItem('Name: ', student['first-name'] + ' ' + student['last-name'])
const heightLi = createListItem('Height: ', student['height'] / 100 + 'm')
const weightLi = createListItem('Weight: ', student['weight'] + 'kg')
root.appendChild(nameLi)
root.appendChild(heightLi)
root.appendChild(weightLi)
document.body.appendChild(root)

是否是變得更短了,也更易讀了?即便你不看createListItem函數的實現,光看const nameLi = createListItem('Name: ', student['first-name'] + ' ' + student['last-name'])也能大體明白這段代碼時幹什麼的。


可是上面的代碼封裝的還不夠,由於每次建立一個列表項,咱們都要多調用一遍createListItem,上面的代碼爲了建立name,height,weight標籤,調用了三遍createListItem,這裏顯然還有精簡的空間。所以,咱們再進一步封裝:


const student = {
    'first-name': 'Jessica',
    'last-name': 'Bre',
    'height': 180,
    'weight': 70,
}

const createList = function(kvPairs){
  const createListItem = function (label, content) {
    const li = document.createElement('li')
    const labelSpan = document.createElement('span')
    labelSpan.textContent = label
    const contentSpan = document.createElement('span')
    contentSpan.textContent = content
    li.appendChild(labelSpan)
    li.appendChild(contentSpan)
    return li
  }
  const root = document.createElement('ul')
  kvPairs.forEach(function (x) {
    root.appendChild(createListItem(x.key, x.value))
  })
  return root
}


const ul = createList([
  {
    key: 'Name: ',
    value: student['first-name'] + ' ' + student['last-name']
  },
  {
    key: 'Height: ',
    value: student['height'] / 100 + 'm'
  },
  {
    key: 'Weight: ',
    value: student['weight'] + 'kg'
  }])
document.body.appendChild(ul)

有沒有看到MVVM風格的影子?student對象是原始數據,至關於Model層;createList建立了dom樹,至關於View層,那麼ViewModel層呢?仔細觀察,其實咱們傳給createList函數的參數就是Model的數據的改造,爲了讓Model的數據符合View的結構,咱們作了這樣的改造,所以雖然這段函數裏面沒有獨立的ViewModel層,可是它確實是存在的!聰明的同窗應該想到了,下一步就是來獨立出ViewModel層了吧~


// Model
const tk = {
    'first-name': 'Jessica',
    'last-name': 'Bre',
    'height': 180,
    'weight': 70,
}

//View
const createList = function(kvPairs){
  const createListItem = function (label, content) {
    const li = document.createElement('li')
    const labelSpan = document.createElement('span')
    labelSpan.textContent = label
    const contentSpan = document.createElement('span')
    contentSpan.textContent = content
    li.appendChild(labelSpan)
    li.appendChild(contentSpan)
    return li
  }
  const root = document.createElement('ul')
  kvPairs.forEach(function (x) {
    root.appendChild(createListItem(x.key, x.value))
  })
  return root
}
//ViewModel
const formatStudent = function (student) {
  return [
    {
      key: 'Name: ',
      value: student['first-name'] + ' ' + student['last-name']
    },
    {
      key: 'Height: ',
      value: student['height'] / 100 + 'm'
    },
    {
      key: 'Weight: ',
      value: student['weight'] + 'kg'
    }]
}
const ul = createList(formatStudent(tk))
document.body.appendChild(ul)

這看上去更舒服了。可是,最後兩行還能封裝~


const smvvm = function (root, {model, view, vm}) {
  const rendered = view(vm(model))
  root.appendChild(rendered)
}
smvvm(document.body, {
      model: tk, 
      view: createList, 
      vm: formatStudent
})


這種寫法,熟悉vue或者regular的同窗,應該會以爲似曾相識吧?


本文來自網易雲社區,經做者顧靜受權發佈。


瞭解網易雲 :
網易雲官網:https://www.163yun.com
網易雲社區:https://sq.163yun.com/blog
網易雲新用戶大禮包:https://www.163yun.com/gift

更多網易研發、產品、運營經驗分享請訪問網易雲社區

相關文章
相關標籤/搜索