跳到主要内容位置

Vue 3 使用 watch 监听数据的变化

张旭乾

我们知道在 Vue 应用中,当 data 里边的属性变化的时候,HTML 模板中使用到这个属性的地方,会自动更新,但是如果我们想在 JavaScript 中,监听某个属性的变化,并做一些操作,该怎么办呢?这个时候可以使用 Vue 提供的 watch 选项,为属性添加一个监听器。

Watch 的应用场景

watch 适合根据属性的变化,做一些开销比较大的操作,或者异步操作,例如设置定时器,请求远程 API 等,它不会返回任何值,而是根据属性的变化,直接修改其它属性的值,这样其它属性修改之后,就能够在 Vue 的响应性机制下,自动更新依赖这个属性的 HTML 视图部分。

Watch 的使用方法

watch 的值也是一个对象,对象里边是一系列的方法,每个方法名跟要监听的属性名保持一致,这里要记住,不能像 computed 计算属性或者 methods 里边的方法那样,自定义方法的名字。

  data() {
return {
showAnswer: false
}
},
watch: {
showAnswer() {
},
},

在监听某个属性的方法中,它接收两个参数,一个是属性变化后的值,一个是属性变化前的值:

watch: {
showAnswer(newVal, oldVal) {
},
},

在方法体里,我们可以进行一些计算量比较大的操作,或者异步操作,例如设置一个间隔定时器:

watch: {
showAnswer(newVal, oldVal) {
if (newVal) {
this.countDown = 5;
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
// 5秒后关闭答案
this.timer = setInterval(() => {
this.countDown -= 1;
if (this.countDown === 0) {
this.showAnswer = false;
clearInterval(this.timer);
this.timer = null;
}
}, 1000);
}
},
},

当方法名对应的属性值发生变化时,这个监听器就会自动执行,并且每发生一次变化都会执行一次。

示例

我们来继续看上一节课的示例,在上节课,我们使用 methods 配置,给按钮加上了点击事件处理函数,这节课我们假设在显示题目的答案之后,程序会自动添加一个倒数计时器,5 秒之后自动隐藏答案,并把倒数的秒数显示在按钮的文本标签的后面,我们来看看如何利用 watch 来实现。 首先打开我们项目的代码,打开 index.js 文件,我们添加一个新的属性,countDown,来保存当前的倒数秒数,初始值为 5:

  data() {
return {
showAnswer: false,
countDown: 5,
};
},

之后把 countDown 加到按钮的文本标签中,在 computed 里的 label() 中,如果此时是显示答案的,那么就把倒计时秒数添加到按钮文本的后面,也就是当按钮显示为『隐藏答案』的时候:

computed: {
label() {
return this.showAnswer ? "隐藏答案 " + this.countDown : "显示答案";
},
},

接着我们再添加一个 timer 属性,保存间隔定时器的 id,用于后续清除定时器,它的默认值为 null,也就是说目前还没有创建定时器:

data() {
return {
showAnswer: false,
countDown: 5,
timer: null,
};
},

之后我们在 methods 下边新添加一个 watch 配置项,它的值是一个对象:

  watch: {

},

在它的里边,我们定义一个名字为showAnswer的方法,这个方法名跟 data 中的 showAnswer 属性保持一致,这个是必须的,这样每次 showAnswer 属性值发生变化时,都会调用 watch 中的 showAnswer 方法:

watch: {
showAnswer() {
},
},

showAnswer 方法接收两个参数,第一个是变化后的值,第二个是变化前的值,我们给变化后的值命名为 newVal,这个可以自定义,变化前的值我们不需要,可以不用管它:

watch: {
showAnswer(newVal, oldVal) {
},
},

接下来,在方法体中判断,如果 newVal 为 true,也就是说当前 showAnswer 属性的值变为了 true,显示了答案,那么我们把 this.countDown 的值先还原为 5,因为上一次的计时器可能已经把它改为 0 了:

watch: {
showAnswer(newVal, oldVal) {
if (newVal) {
this.countDown = 5;
}
},
},

(这里可以先展示一下错误示范,再回过来加上这段代码)接着判断如果 timer 有值的话,先把它清除,这里是因为有可能倒计时还没有结束,用户就手动点击了隐藏答案,然后又点击了显示答案,这个时候定时器不会自己清除,反复点击的话会注册多个定时器,多个定时器会同时修改 countDown 的值,这样会导致倒计时出现数值减的过快,或数值减的过多的问题:

watch: {
showAnswer(newVal, oldVal) {
if (newVal) {
this.countDown = 5;
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
},
},

接下来是正常的情况下,我们使用 setInterval() 创建一个间隔定时器,间隔时间为 1 秒:

watch: {
showAnswer(newVal, oldVal) {
if (newVal) {
this.countDown = 5;
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
// 5秒后关闭答案
this.timer = setInterval(() => {

}, 1000);
}
},
},

在间隔定时器的回调函数中,我们把倒计时秒数 countDown 属性进行减 1 操作,然后判断如果倒计时秒数等于 0,那么就把显示答案的属性 showAnswer 设置为 false,隐藏答案,然后清除掉当前的计时器,把 timer 属性还原成 null:

watch: {
showAnswer(newVal, oldVal) {
if (newVal) {
this.countDown = 5;
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
// 5秒后关闭答案
this.timer = setInterval(() => {
this.countDown -= 1;
if (this.countDown === 0) {
this.showAnswer = false;
clearInterval(this.timer);
this.timer = null;
}
}, 1000);
}
},
},

这样就实现了倒计时的过程,我们运行一下项目看一下效果,点击显示答案之后,可以看到倒计时秒数添加到了按钮的文本后面,之后等待 5 秒,可以看到答案就自动隐藏了。这里 watch 就是根据 showAnswer 的值的变化而自动执行了,我们在里边判断,如果当前为显示答案,那么就添加一个间隔定时器。 我们也可以试试反复点一下按钮,也可以看到倒计时也还是能正常的进行。

小结

这个就是 watch 的使用方法,当需要在 Vue 的 js 代码中监听 data 属性的变化时,我们就在 watch 配置对象里,添加一个同名的方法,方法接收两个参数,一个是属性变化后的值,一个是属性变化前的值,这样就可以在方法里边根据属性值,做一些异步的或者耗时的业务逻辑了。

提示

《Vue 3.x 全家桶完全指南与实战》课程已上线,包括 Vue 3.x、TypeScript、Vue Router 4.x、Vuex 4.x 所有初级到高级的语法特性详解,让你完全胜任 Vue 前端开发的工作。点击查看详情。

《React即时通信UI实战》课程,利用 Storybook、Styled-components、React-Spring 打造属于自己的组件库。

新书《JavaScript 基础语法详解》已上架,可在京东、当当、淘宝等各大电商购买