通过前面实现的Watcher
和Compiler
,我们已经能够监听数据的改动以及解析模板语法。现在就让我们将他们结合起来,实现数据驱动模板。
结构
根节点
绝大多数的 MVVM 框架都会指定挂载的根节点,框架只会对根节点内部进行操作,作用不言而喻。
数据
在一个 mvvm(ViewModel)
作用域中,需要定义一组用于驱动 ui 的数据data
。由于我们在Watcher
中是通过Object.defineProperty
来监听数据更改的,也就是说data
的字段是必须事先声明好的。
假设data
有一下结构
1 | const owner: any = {}; |
上面的例子中,因为sex
并未在初始时data
中定义,所以修改sex
不会触发事件。
计算属性
在模板上进行复杂的计算并不是明智的原则。比如接口返回的数据gener
是有1|2|0
三个值,我们要在模板上将其中文显示。
1 | <div>{{ gender===1?'男':(gender ===2?'女':'未知')}}</div> |
看着都头疼,所以我们提供计算属性来完成此类工作。
1 | { |
1 | <div>{{ genderText }}</div> |
这样是不是清爽多了呢?
数据监听
通过watcher
可以很方便的监听数据的改动,但是这里我们可以提供一个更方便的 api。
如需要监听formData.name
改动进行关键字匹配搜索时
1 | { |
生命周期
mvvm 有自己的声明周期,created
,destroyed
等
方法和事件回调
1 | { |
汇总
将上面的结构汇总可以得到一个借鉴(山寨)vue 的 MVVM 模型对象结构。
1 | new MVVM({ |
实现
Constructor
首先实现构造函数,构造函数接受一个参数,类型为上面定义的数据结构。
MVVM
类需要实现IOwner
接口,这样Watcher
和Complier
内部才能够正常运行。
1 | interface MVVMConfig { |
除了初始化Watcher
和Compiler
,构造函数还依次做了以下几个步骤
initMethods
初始化方法和回调函数,仅仅是将传入的methods
方法代理到this
上执行而已。
1 | Object.keys(this.config.methods || {}).forEach(key => { |
initComputed
这里有一个优化点,如果按这种的写法,我们无法得知计算属性内部监听了哪些属性,只能全局监听。也就是说任何数据的改动都会触发计算属性的执行。
1 | ageText() { |
所以我们对写法进行一些改造,显式声明依赖项,这样一来仅在age
发生变化时才执行计算属性。
1 | { |
计算属性实现并不复杂,只是通过了Watcher
对数据的变化进行了监听,并执行计算属性函数得到返回值,并将键值作为事件名触发Watcher
的变化,这样在模板上监听了这个键值的元素就能实时更新了。
1 | private initComputed() { |
initWatch
初始化观察方法,实际上是Watcher
的一个语法糖,非常简单。
1 | private initWatch() { |
初始化Compiler
因为模板需要的很多属性需要在初始化后才能得出,所以Compiler
的初始化放到最后执行。
created
最后执行声明周期created
,这个时候 dom 元素已经挂载,模板绑定已经处理完毕。
试一试
我们实现一个简单的count
试一试。
你可以打开demo中的 count.html 查看效果
1 |
|
下一步
目前我们的初级目标已经达成了,但是功能上还很简单,下一篇我们将实现前面提到过的指令(directive),用来增强模板的功能。