一個只有十行的精簡MVVM框架(下篇)
本文來自網易雲社區。
讓我們來加點互動
前面學生信息的身高的單位都是默認m
,如果新增一個需求,要求學生的身高的單位可以在m
和cm
之間切換呢?
首先需要一個變量來保存度量單位,因此這裏必須用一個新的Model:
const tk = { ‘first-name‘: ‘Jessica‘, ‘last-name‘: ‘Bre‘, ‘height‘: 180, ‘weight‘: 70, } const measurement = ‘cm‘
為了讓tk
更方便的被其他模塊重用,這裏選擇增加一個measurement
數據源,而不是直接修改tk
。
在視圖部分要增加一個radio單選表單,用來切換身高單位。
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 createToggle = function (options) { const createRadio = function (name, opt){ const radio = document.createElement(‘input‘) radio.name = name radio.value = opt.value radio.type = ‘radio‘ radio.textContent = opt.value radio.addEventListener(‘click‘, opt.onclick) radio.checked = opt.checked return radio } const root = document.createElement(‘form‘) options.opts.forEach(function (x) { root.appendChild(createRadio(options.name, x)) root.appendChild(document.createTextNode(x.value)) }) return root } const createToggleableList = function(vm){ const listView = createList(vm.kvPairs) const toggle = createToggle(vm.options) const root = document.createElement(‘div‘) root.appendChild(toggle) root.appendChild(listView) return root }
接下來是ViewModel
部分,createToggleableList
函數需要與之前的createList
函數不同的參數。因此,對View-Model結構重構是有必要的:
const createVm = function (model) { const calcHeight = function (measurement, cms) { if (measurement === ‘m‘){ return cms / 100 + ‘m‘ }else{ return cms + ‘cm‘ } } const options = { name: ‘measurement‘, opts: [ { value: ‘cm‘, checked: model.measurement === ‘cm‘, onclick: () => model.measurement = ‘cm‘ }, { value: ‘m‘, checked: model.measurement === ‘m‘, onclick: () => model.measurement = ‘m‘ } ] } const kvPairs = [ { key: ‘Name: ‘, value: model.student[‘first-name‘] + ‘ ‘ + model.student[‘last-name‘] }, { key: ‘Height: ‘, value: calcHeight(model.measurement, model.student[‘height‘]) }, { key: ‘Weight: ‘, value: model.student[‘weight‘] + ‘kg‘ }, { key: ‘BMI: ‘, value: model.student[‘weight‘] / (model.student[‘height‘] * model.student[‘height‘] / 10000) }] return {kvPairs, options} }
這裏為createToggle
添加了ops
,並且將ops
封裝成了一個對象。根據度量單位,使用不同的方式去計算身高。當任何一個radio
被點擊,數據的度量單位將會改變。
看上去很完美,但是當你點擊radio標簽的時候,視圖不會有任何改變。因為這裏還沒有為視圖做更新算法。有關MVVM
如何處理視圖更新,那是一個比較大的課題,需要另辟一個博文來講,由於本文寫的是一個精簡的MVVM
框架,這裏就不再贅述,並用最簡單的方式實現視圖更新:
const smvvm = function (root, {model, view, vm}) { let m = {...model} let m_old = {} setInterval( function (){ if(!_.isEqual(m, m_old)){ const rendered = view(vm(m)) root.innerHTML = ‘‘ root.appendChild(rendered) m_old = {...m} } },1000) } smvvm(document.body, { model: {student:tk, measurement}, view: createToggleableList, vm: createVm })
上述代碼引用了一個外部庫lodash
的isEqual
方法來比較數據模型是否有更新。此段代碼應用了輪詢,每秒都會檢測數據是否發生變化,有變化了再更新視圖。這是最笨的方法,並且在DOM結構比較復雜時,性能也會受到很大的影響。還是同樣的話,本文的主題是一個精簡的MVVM框架,因此略去了很多細節性的東西,只把主要的東西提煉出來,以達到更好的理解MVVM模式的目的。
MVVM框架的誕生
以上便是一個簡短精簡的MVVM風格的學生信息的示例。至此,一個精簡的MVVM框架其實已經出來了:
/** * @param {Node} root * @param {Object} model * @param {Function} view * @param {Function} vm */ const smvvm = function (root, {model, view, vm}) { let m = {...model} let m_old = {} setInterval( function (){ if(!_.isEqual(m, m_old)){ const rendered = view(vm(m)) root.innerHTML = ‘‘ root.appendChild(rendered) m_old = {...m} } },1000) }
什麽?你確定不是在開玩笑?一個只有十行的框架?
請記住: 框架是對如何組織代碼和整個項目如何通用運作的抽象。
這並不意味著你應該有一堆代碼或混亂的類,盡管企業可用的API列表經常都很可怕的長。但是如果你研讀一個框架倉庫的核心文件夾,你可能發現它會出乎意料的小(相比於整個項目來說)。其核心代碼包含主要工作進程,而其他部分只是幫助開發人員以更加舒適的方式構建應用程序的附件。有興趣的同學可以去看看cycle.js,這個框架只有124行(包含註釋和空格)。
總結
此時用一張圖來作為總結再好不過了!
本文來自網易雲社區,經作者顧靜授權發布。
了解網易雲 :
網易雲官網:https://www.163yun.com
網易雲社區:https://sq.163yun.com/blog
網易雲新用戶大禮包:https://www.163yun.com/gift
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
一個只有十行的精簡MVVM框架(下篇)