手撕Vue-编译指令数据
经过上一篇的分析,完成了查找指令和模板的功能,接下来就是编译指令的数据了。
所以本章节主要处理的方法则是 buildElement 方法,我们先分析一下我们所拿到的数据在进行编码,这样会更加清晰一些。
我将 name, value 打印出来,分别对应的值是 name: v-model, value: name,在今后我们的命令中可不止只有 v-model,还有 v-text、v-html、v-on 等等,所以我们需要对这些指令进行分类,然后再进行编译。
所以我这里特意定义了一个工具类叫 CompilerUtil,用来处理指令的分类,代码如下:
let CompilerUtil = {
/**
* 处理 v-model 指令
* @param node 当前元素
* @param value 指令的值
* @param vm Nue 的实例对象
*/
model: function (node, value, vm) {
},
html: function (node, value, vm) {
},
text: function (node, value, vm) {
}
}
然后我们在 buildElement 方法中调用这个方法,代码如下:
// 解构 name
let [, directive] = name.split('-');
// v-model -> [v, model]
// 2.根据指令名称, 调用不同的处理函数
CompilerUtil[directive](node, value, this.vm);
这样我们就可以根据指令的名称,调用不同的处理函数了。
接下来我们就来处理 v-model 指令,代码如下:
/**
* 处理 model 指令
* @param node 当前元素
* @param value 指令的值
* @param vm Nue 的实例对象
*/
model: function (node, value, vm) {
node.value = vm.$data[value];
},
这样我们就可以将数据渲染到页面上了,打开浏览器,可以看到效果如下:
v-model 指令已经可以正常使用了,但是还有问题,就是我们的数据结构目前是比较简单的,那么如果我们的数据是一个对象呢,例如:
time: {
h: 10,
m: 10,
s: 10
}
在用 input 绑定 v-model 进行渲染发现,只有第一个 input 能够正常渲染,其他的 input 都是 undefined,这是为什么呢?
<input type="text" v-model="time.h">
<input type="text" v-model="time.m">
<input type="text" v-model="time.s">
那么这里就要去看一下我们 model 方法的实现了,如果是 time.h,value 等于的值为 time.h, 然后我们在执行 vm.$data[value]
就变为了 vm.$data[time.h]
, 正常的获取这种数据结构的方式应该是先 vm.$data[time]
拿到 time 对象,然后再 time[h]
拿到 h 的值,所以我们需要对这种数据结构进行处理,为了已维护,我这里单独抽离了一个方法出来进行处理获取 value,方法名字叫做 getValue,代码如下:
getValue(vm, value) {
// time.h --> [time, h]
return value.split('.').reduce((data, currentKey) => {
// 第一次执行: data=$data, currentKey=time
// 第二次执行: data=time, currentKey=h
return data[currentKey];
}, vm.$data);
},
reduce
方法被用于迭代这个字符串数组。它接受一个回调函数,这个回调函数在每次迭代中被调用。在这个回调函数中,data 是上一次迭代的结果,而 currentKey 是当前迭代的数组元素(键路径中的一个部分)在每次迭代中,回调函数通过 data[currentKey]
的方式访问嵌套对象的属性,然后将这个属性的值作为下一次迭代的 data, 最终,reduce 方法将遍历整个键路径,直到达到最深层的属性,然后返回该属性的值。这样我们就可以正常的获取到数据了,最后在改造一下之前 model 方法获取值的地方,调用下刚刚编写的 getValue 方法即可:
model: function (node, value, vm) {
node.value = this.getValue(vm, value);
},
再次打开浏览器,可以看到效果如下:
这个搞定之后,我们紧接着把 v-html 和 v-text 也搞定,代码基本上都是一样的,只是渲染的方式不一样,代码如下:
/**
* 处理 html 指令
* @param node 当前元素
* @param value 指令的值
* @param vm Nue 的实例对象
*/
html: function (node, value, vm) {
node.innerHTML = this.getValue(vm, value);
},
/**
* 处理 text 指令
* @param node 当前元素
* @param value 指令的值
* @param vm Nue 的实例对象
*/
text: function (node, value, vm) {
node.innerText = this.getValue(vm, value);
}
编写测试代码:
html: `<div>我是div</div>`,
text: `<div>我是div</div>`
编写HTML代码:
<div v-html="html">abc</div>
<div v-text="text">123</div>
打开浏览器,可以看到效果如下: