手撕Vue-数据驱动界面改变下

发布时间 2023-10-20 00:03:42作者: BNTang

经过上一篇的介绍,数据驱动界面改变 v-model 的双向绑定已告一段落, 剩余的就以这篇文章来完成。

首先完成我们的 v-html,v-text, 其实很简单,就是将我们之前的 v-model 创建观察者的方法,在 v-html 和 v-text 中再写一次即可,创建属于 v-html 和 v-text 的观察者。

v-html:

html: function (node, value, vm) {
    new Watcher(vm, value, (newValue, oldValue) => {
        node.innerHTML = newValue;
    });
    node.innerHTML = this.getValue(vm, value);
},

v-text:

text: function (node, value, vm) {
    new Watcher(vm, value, (newValue, oldValue) => {
        node.innerText = newValue;
    });
    node.innerText = this.getValue(vm, value);
},

测试一下 v-html,打开浏览器控制台,输入 vue.$data.html = '<p>我是测试v-html<p/>',可以看到界面上的内容已经改变了。

image-20231019224801735

测试一下 v-text,打开浏览器控制台,输入 vue.$data.text = '<p>我是测试v-text<p/>',可以看到界面上的内容已经改变了。

image-20231019224912964

好了到此为止,指令的数据驱动界面改变已经完成了,接下来我们来完成模板语法的数据驱动界面改变。

这个就与之前的指令的数据驱动界面改变不同了,好了先不说问题,我们先直接来看代码一步一步分析。

我看了下之前处理 content 的代码发现,获取不到对应的属性名称叫什么,因为是直接调用 this.getContent(vm, value); 获取的,所以会出现一个问题就是给 content 创建观察者对象的时候不能直接告诉他我要监听的是哪个属性,所以我就想到了一个办法。

首先将之前的代码注释掉,再然后我编写一个正则表达式,关于这个正则表达式在之前的文章中有讲到,大概意思就是匹配 {{}} 中的内容,这里就不再赘述了。

let reg = /\{\{(.+?)\}\}/gi;

继续往下看,我利用 value 调用了 replace 方法,传递了两个参数,第一个参数是刚刚编写的正则表达式,第二个参数是一个函数,这个函数的作用就是将匹配到的内容替换成对应的值,我先将其返回值打印出来,看看是什么,我们的代码就可以写成这样。

content: function (node, value, vm) {
    // console.log(value); // {{ name }} -> name -> $data[name]
    // node.textContent = this.getContent(vm, value);

    let reg = /\{\{(.+?)\}\}/gi;

    value.replace(reg, (...args) => {
        console.log(args[1].trim());
    });
}

image-20231019230456960

是的,我们的确获取到了对应的属性名称,接下来我们就可以利用这个属性名称来创建观察者对象了,我们的代码就可以写成这样。

content: function (node, value, vm) {
    // console.log(value); // {{ name }} -> name -> $data[name]
    // node.textContent = this.getContent(vm, value);

    let reg = /\{\{(.+?)\}\}/gi;

    node.textContent = value.replace(reg, (...args) => {
        const attr = args[1].trim();
        new Watcher(vm, attr, (newValue, oldValue) => {
            node.textContent = this.getContent(vm, value);
        });
        return this.getValue(vm, args[1]);
    });
}

好了,我们来测试一下,打开浏览器控制台,输入 vue.$data.name = '我是测试 {{ name }}',可以看到界面上的内容已经改变了。

image-20231019230944283

一切看起来都很完美,最终版代码其实是我没有将坑点说出来,现在我们来看看这个坑点是什么,再看之前,我来讲述一下为什么是又调用了 this.getContent 方法而不是直接将 newValue 赋值给 node.textContent。

假如我们的数据结构是这样的 {{ name }} - {{ age }} 如果是通过直接将 newValue 赋值给 node.textContent 的话,这个时候呢,我假设 name 的值是 BNTang, age 的值是 33,那么界面上第一次加载的内容会是 BNTang - 33,但是如果我将 name 的值改成了 xhh,那么界面上的内容就会变成 xhh,这个时候 age 的值就丢掉了,如下图是我的测试结果。

image-20231019233644556

原因就是直接替换掉了,所以在动态更改 name 属性或者 age 属性其中一个的情况下,还需要将 {{ name }} - {{ age }} 这样的内容替换成 BNTang - 33,这样的话,我们就需要调用 this.getContent 方法,这个方法就会利用正则挨个匹配 {{}} 中的内容,然后再将其替换成对应的值,这样就不会出现上面的问题了。

?> 最后总结一下 content 函数的 value.replace 在外层是为了拿到属性名称,内层是为了保证数据完整性。