Vue的v-model指令与方法绑定执行顺序所导致的数据更新不及时问题

在实际的vue项目开发中,可能会遇到v-model指令双向绑定的数据与标签绑定方法所回调的数据不同步的问题。这是由于v-model与标签绑定方法执行顺序之差所导致的。解决的办法是利用绑定方法先执行的方式来手动模拟v-model,进而实现数据的更新。

问题复现

观察如下的html代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> v-model与方法绑定的数据更新解决方案 </title>

    <!-- 导入2.6.12版本的vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>

    <main id="root">
        <p>
            <input type="radio" name="langChoiced" v-model="lang" value="Java">Java
            <input type="radio" name="langChoiced" v-model="lang" value="C++">C++
            <input type="radio" name="langChoiced" v-model="lang" value="C#">C#
        </p>
        <p>
            你选择的内容 {{ lang }}
        </p>
    </main>
    <!-- 导入挂载到根组件模板上的vue实例对象。 -->
    <script src="js.js"></script>
</body>

</html>

其定义在js.js文件内的vue对象如下:

new Vue({
    el:"#" + "root",
    data: {
        lang: "预设的空值",
    }
});

v-model指令应用在同名的单选框中,可以用于实现输入数据的实时获取。lang变量实时获取input单选框选择的值。现在的页面可以实现v-model绑定值在页面上的实时渲染。

我们现在给每一个输入框绑定一个点击事件。目的是让输入框不仅实现v-model数据的实时获取,还去执行其他的行为。

<input type="radio" name="langChoiced" v-model="lang" @click="showData()" value="Java">Java
<input type="radio" name="langChoiced" v-model="lang" @click="showData()" value="C++">C++
<input type="radio" name="langChoiced" v-model="lang" @click="showData()" value="C#">C#

其绑定的点击事件对应的方法,写法如下:

new Vue({
    el: "#" + "root",
    data: {
        lang: "预设的空值",
    },
    methods: {
        showData() {
            console.log(this.lang);
        },
    },
});

绑定的方法只需要在控制台中实时输出v-model绑定的值。但是在执行的时候会出现这样的结果,如下图:
在这里插入图片描述

我们先点击C#选项,按理说页面渲染的lang变量值与console显示出来的值应该是一样的,但是结果是:v-model实时渲染的值没有错误,但是console显示出来的值没有及时更新。

我们试着再点击另外一个C++按钮,得到如下的结果:

在这里插入图片描述

页面仍然实时渲染说选择的C++选项值,但是console显示出来的确实上一次点击的值C#。数据的更新存在着滞后现象,并不是我们预设的同步更新数值。

v-model的执行顺序在被绑定方法调用之后

这个结论是可以从上述的现象中推断出来的。

在第一次的点击中,先执行绑定的方法,显示出当前的数值,所以先显示出了预设的空值。然后再执行v-model指令,更新数据,数据更新为C#,并渲染到页面上。

在第二次的点击中,先执行绑定的方法,显示出当前的数值,所以先显示出了C#。然后再执行v-model指令,更新数据,数据更新为C++,并渲染到页面上。

点此参考其他人对该结论的验证过程。

v-model指令的本质是方法绑定的语法糖

我们先看看vue的官方文档,看看v-model指令的本质是什么,如下:

<ChildComponent v-model="pageTitle" />

<!-- 是以下的简写: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

上述例子来源请点击此处。

因此我们可以说,v-model指令的本质是方法绑定的语法糖。其本质是一个input事件。

调用事件event来实现数据的同步更新

现在反思一下,在同时使用v-model和方法绑定时,相当于同时执行了两个绑定的事件,这两个事件执行具有先后。先执行被绑定的事件,再执行实现v-model指令的input事件。

由于事件执行有先后顺序,导致前者执行的事件不能利用v-model实现数据的实施获取。能不能考虑将这两个事件全部整合在一起? 在一个事件中,不仅完成默认要执行的功能,还实现了v-model本应该实现的数据获取与更新。因此,我们要考虑手动模拟v-model,用另外的方式实现数据在输入框中的获取与数据的实施更新。

我们对showData函数进行修改,如下:

showData(e) {
    this.lang = e.target.value;
    console.log(this.lang);
},

给函数增加一个新的形参e,表示点击事件这个事件对象。e.target.value 的含义是获取当前输入框中选中的值,相当于手动实现v-model的数据获取this.lang 的赋值过程,相当于手动实现v-model的数据同步

值得注意的是,在html内的标签中,写法要去除绑定函数的括号,只写函数名称。如下:

<input type="radio" name="langChoiced" v-model="lang" @click="showData" value="Java">Java
<input type="radio" name="langChoiced" v-model="lang" @click="showData" value="C++">C++
<input type="radio" name="langChoiced" v-model="lang" @click="showData" value="C#">C#

上述的写法,已经实现了两个事件的整合。不仅实现了数据的实时更新,还保证了原先业务逻辑的正常运行与数据更新。问题已经得到解决。测试的结果如下:

在这里插入图片描述

但是在实际应用中,我们不可能在标签内,总是写成无参数的函数形式。我们要写成可以自由加载形参的形式。利用vue自带的特殊变量$event,其改进的写法如下:

<input type="radio" name="langChoiced" v-model="lang" @click="showData($event)" value="Java">Java
<input type="radio" name="langChoiced" v-model="lang" @click="showData($event)" value="C++">C++
<input type="radio" name="langChoiced" v-model="lang" @click="showData($event)" value="C#">C#
showData(parm) {
    this.lang = parm.target.value;
    console.log(this.lang);
},

这样的写法是有明显的缺点的。 当我们把两个事件合并在一起时,其功能就发生了明显的混杂,代码的耦合度较高,不利于后期维护。相当于把v-model功能与原来要有的功能完全合并在一起来维护。也带来了一定的阅读困难。

Logo

前往低代码交流专区

更多推荐