一个只有十行的精简MVVM框架(下篇)

本文来自网易云社区

让我们来加点互动


前面学生信息的身高的单位都是默认m,如果新增一个需求,要求学生的身高的单位可以在mcm之间切换呢?


首先需要一个变量来保存度量单位,因此这里必须用一个新的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 
})


上述代码引用了一个外部库lodashisEqual方法来比较数据模型是否有更新。此段代码应用了轮询,每秒都会检测数据是否发生变化,有变化了再更新视图。这是最笨的方法,并且在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

更多网易研发、产品、运营经验分享请访问网易云社区



posted @ 2018-08-20 16:00  tianshidan1998  阅读(182)  评论(0编辑  收藏  举报