天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。

0%

仿造vue模板写一个自定义模板插入数据的小demo

为撒子突然想到写这个了

    最近正在看vue源码实现机制的视频,里面讲到了如何实现模板替换的问题,听了课之后觉得光听不练还是菜,所以最后决定仿造视频的例子在自己用自己的理解写一遍,以此加深记忆。

开始,先写一个html

    既然要实现模板替换,那么不可缺少的html先得拿出来,主体html代码如下:

1
2
3
4
5
6
7
8
9
<div id="root" class="root">
<div>
<div>
<p>{{name}}--{{msg}}</p>
</div>
</div>
<p>{{sex.sex1.male}}</p>
<p>{{sex.sex1.female}}</p>
</div>

来点儿数据加点料

    这里我们在html自定义了模板写法{ { } },里面包含了我们想要测试的一些字段,接下来就是数据了,这里我提供了如下数据:

1
2
3
4
5
6
7
8
9
10
data: {
name: '我都名字',
msg: '有条消息',
sex: {
sex1: {
male: '男',
female: '女',
}
}
}

    因为是自己写出来测试的缘故,就随便写死了些数据方便测试。我们都知道Vue的话引入了库之后在js里面new一个Vue的实例,参数为一个对象,包含要挂载到的节点属性名,数据,这里简单处理我们就只提供了节点和数据了。从上面的html代码里可以看到,我们准备挂载的节点属性名为root,参照vue挂载实例的方式我们可以写出下面的代码来:

1
2
3
4
5
6
7
8
9
10
11
12
13
new newVue({
el: "#root",
data: {
name: '我都名字',
msg: '有条消息',
sex: {
sex1: {
male: '男',
female: '女',
}
}
}
})

正菜在这里

    而要实现上面的这个newVue构造函数的话,我们需要对传入的参数进行处理。这里理一下思路:
1. 先用cloneNode(true)方法拷贝一份原dom节点的数据。因为我们不能直接修改原dom,不然后面如果有其他的数据都没法继续渲染了。(模板是不变的,变得是数据)
2. 传入的节点进行递归遍历,查找到所有格式为{ { } }的文本节点,并用data中的数据替换文本节点的nodevalue。
3. 将修改过后的新dom元素替换原本的dom节点。
我使用了一个class语法糖的形式进行newVue构造函数的编写,render函数作为渲染函数,compiler函数处理内部递归替换逻辑,update函数使用新节点替换旧节点。相关实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class newVue {
constructor(options) {
this._el = document.querySelector(options.el)
this.$el = this._el.cloneNode(true)
this._parent = this._el.parentNode
this._data = options.data
// 替换原本的节点
this.render()
}
}

function compiler(template, data) {
const reg = /\{\{(.*?)\}\}/g
const childNodes = template.childNodes

for(let i=0; i<childNodes.length; i++) {
const type = childNodes[i].nodeType
// 文本节点
if(type === 3) {
childNodes[i].nodeValue = childNodes[i].nodeValue.replace(reg, function(a, b) {
const key = b.trim()
const finalTxt = this.getMultiLevel(data)
return finalTxt(key)
})
}
// 元素节点
else if(type === 1) {
this.compiler(childNodes[i], data)
}
}
}
function getMultiLevel(data) {
return function(str) {
const strArr = str.split('.')
let res = data
let key = ''
while(key = strArr.shift()) {
res = res[key]
}
return res
}
}

newVue.prototype.render = function() {
this.compiler()
}
newVue.prototype.compiler = function() {
compiler(this.$el, this._data)
this.update()
}
newVue.prototype.update = function() {
this._parent.replaceChild(this.$el, this._el)
}

落幕

    上面代码中还有个我刚刚没提到的函数:getMultiLevel,这个函数采用了柯里化写法,简化了需要传给函数参数的个数,内部则是对多层级模板写法例如:{ {sex.sex1.male} },进行递归遍历,以访问到更深层次的data数据。最后的实现效果我放两张图更直观。

实现之前:

实现之后:

完美!perfect!当然,上面的实现只是一个简化简化再简化的模板替换方案,还没有使用到虚拟dom。虚拟dom的话后面应该也会加上的~