前言
凭空写一个框架是很难的,因此我们在这篇文章中将要尝试去写一些简单的应用。我们从基础的JavaScript
开始,然后将其重构为基于MVVM
模式的程序。
我把所有的代码都放在了JSbin上,并且用了babel/ES6
模式。如果你对任何地方有困惑,请别犹豫,去那里试着敲一下。
用MVVM
方式写你的代码
基本JS
编写学生信息
我们继续用上篇文章中提到的学生信息。如果我们想要写这样的一个app,我们应该从下面的代码开始:
|
|
其结果类似于下面:
- Name: Tracy Kent
- Height: 1.7m
- Weight: 50kg
一个三行的列表却花费了一大推代码,这真恐怖。
重构可复用性
为什么程序员着迷于各种最佳实践?这是他们懒惰的结果。
怠惰是程序员的美德。
这个行业最伟大的想法之一就是“重用”。我们现在的代码里面包含了一大堆重复的行,而程序设计中最广泛接受的规则之一是“DRY”:
Do not Repeat Yourself
现在我们让这个App更加Drier:
我们会发现,我们写了好几遍document.createElement
来为列表创建HTML节点,但实际上我们没有必要这样做,因为所有的列表项都具有相似的结构。
对,那应该是一个共享函数。
我们首先复制name
行的代码,将其放在function
中:
|
|
上面这段代码不会起作用,让我们来修复它:
|
|
因此,这整个App就变成了这样:
|
|
更短了并且可读性更强了。在刚开始混乱的Node-creation
的代码中,你无法知道我在做什么。但是在新的版本中,很明显地可以看出来我正在创建一个列表及其列表项。对于那些读你代码的人,他们可能不关心你是如何创建一个列表项的,他们只知道你在创建列表项。对于那些对列表项有兴趣的人,他们可以去参阅createListItem
函数。
他们可能并不关心你如何创建你的列表,因而代码可以转变如下:
|
|
MVVM更进一步
现在我们的App看上去有点像MVVM风格了。student
对象是我们的原始数据,在我们的重构中它从来没有变过。我们可以称呼它为’Model‘。createList
函数返回了一个DOM树,因此可以叫它’View‘。那么’View-Model‘呢?不幸的是,目前为止我们还没有独立的‘View-Model‘。我的意思是,现在的’View-Model‘还不是独立的,但是确实是存在的。我们传给createList
函数的参数就是’Model‘的改造。也就是说,我们通过人工创建的数组来将’Model‘向’View‘适配。
让我们隔离它:
|
|
这看上去更好了,除了最后两行……
好吧,让我们封装它们:
|
|
需求改变: BMI
我们的产品叫为BMI(身体质量指数)新添加一行。使用原始的代码来做这个很烦人,我不会在这里做的。我恨复制粘贴document.createElement
几十次。
作为对比,对于MVVM版本确是容易的:我们只要修改’View-Model‘就可以了,因为身体质量指数可以从身高和体重计算得来。
|
|
我们可以这样做,或者在函数内做一些进一步的优化,但这不是我们这里的关注的点。我想说的是:为什么我们选择改变’View-Model’?
在MVVM模式中,如果需要改变,我们总是将改变’View-Model’作为首要选择。我觉得这个不难理解:
View可用于显示其他数据集,它只关注数据如何显示。Model可以以其他形式显示,它只关注业务所做的工作。
他们都有重用的潜力,因此我们最好把它们做成通用的。
View-Model是很难被重用的功能。这是一个特定View和某个Model之间专用的适配器。
由于它是专用的,修改它不会使您面临破坏程序的其他部分的风险。但是,如果您想使用View或Model做某事,则需要检查解决方案中所有使用它们的地方。
切换高度度量
在中国有一个玩笑话:一个程序员可以和任何人做朋友除了产品经理。因为产品经理们总是改变他们的需求。
想象一下产品经理叫你添加一个切换来改变高度的度量…
实际上,我不想在这里解释很多如何管理用户输入。这有点复杂,所以我打算在以后的文章里做。但用户输入在UI开发中非常重要,我认为有必要在这个问题上说几句话。
要添加按钮,我们需要修改我们的视图。我们的视图可能被别人重复使用,所以我们不应该轻率地改变现在的视图。这里我们将复用旧的代码来结合一些新的代码。首先,我们需要一些东西来代替现在的度量,所以我们必须调用一个新的Model:
|
|
我们添加了一个measurement
数据源,而不是修改tk
,这样tk
可以仍然被其他模块使用。
对于View部分,我们可以重用列表视图作为新视图的一部分:
|
|
createToggle
函数返回一系列单选框按钮表单。但是从目前的代码来看,我们不知道这个在我们的App中扮演了什么角色。换句话说,这是业务隔离的。
最后,View-Model部分:我们可以看到,createToggleableList
函数需要与之前的createList
函数不同的参数。因此,对View-Model结构重构是有必要的:
|
|
我们为createToggle
添加了ops
,并且将ops
封装成了一个对象。根据度量单位,我们使用不同的方式去计算height
。当任何一个radio
被点击,数据的度量单位将会改变。
看上去很完美,但是当你点击radio
按钮的时候它不会有效果,因为我们没有为数据改变做更新算法。这个部分,有关MVVM框架如何处理数据更新有点扭曲(可以认为不困难)。我想把它放在下一篇文章中。这里,我们将用最简单的方式来实现它:
|
|
这种机制在计算机科学中被称为“轮询”。这在浏览器app中不是一个好方法,虽然它被浏览器广泛应用。这里我们引入了一个外部库,我真是太懒了以至于我不想自己写一个areEqual
函数,因此我用lodash来检测数据模型的更新。
run
函数每秒都会检查数据更新是否发生:如果发生了,我们将会重新渲染整个视图(如果有很多DOM节点的话,这将导致性能问题);如果没有,我们不会做任何事情,继续等待下一秒。
这就是简单的MVVM风格的App的示例,下一篇文章我们将基于这个App创建一个小型MVVM框架。