Vue.js 3 - Composition API使用详解6(watchEffect函数、监听数据变化)
作者:hangge | 2026-04-30 09:24
我们知道使用 Options API 能够监听 data、props 或 computed 数据的变化,比如当数据变化时执行某些操作。而在 Composition API 中,我们可以使用 watchEffect 和 watch 函数完成响应式数据的监听。其中,watchEffect 函数用于自动收集响应式数据的依赖,而 watch 函数需要手动指定监听的数据源。 本文我先介绍 watchEffect 函数的使用。
(2)浏览器中显示的效果下图所示。watchEffect 的回调函数默认先执行一次,打印出 age:28。当单击“修改 age”按钮改变 age 时,watchEffect 会监听到 age 发生变化。此时,watchEffect 的回调函数会再次执行, 并打印出 age:29
(2)测试一下,watchEffect 的回调函数会默认先执行一次,打印出 age:28。当单击“修改 age” 按钮改变 age 时,如果 age 大于 30,由于调用了 watchEffect 返回的 stop 函数,watchEffect 会取消对 age 变量的监听。
(3)运行效果如下,刷新页面,立马连续单击 3 次“修改 age”按钮,watchEffect 函数监听到 age 改变了 3 次,并在每次将重新执行 watchEffect 函数的回调函数时,先执行 onInvalidate 函数中的回调函数来清除副作用,即清除了上一次的定时器。 因此,只有最后一次的定时器没有被清除。
(2)在浏览器中刷新页面,可以看到控制台会打印两次:

(2)下面样例通过传递 flush:"post" 对象参数将副作用函数的执行时机延迟到组件渲染之后再执行:
(3)在浏览器中刷新页面,可以看到这次控制台只打印了一次,打印了“<h4>hangge.com</h4>”元素。
六、watchEffect 函数
1,watchEffect 函数说明
(1)watchEffect 函数的参数需要接收一个函数。该函数会被立即执行一次,并且在执行的过程中会收集依赖。
(2)当收集的依赖发生变化时,watchEffect 函数的参数传入的函数(即副作用函数)才会再次执行。
(3)watchEffect 函数的参数传入的函数不会接收到新值和旧值。
2,基本用法
(1)下面样例在 setup 函数中调用了 watchEffect 函数,并向该函数传递了一个回调函数。传入的回调函数会被立即执行一次,并会在执行过程中收集依赖,比如收集 age 的依赖。当收集的依赖发生变化时,watchEffect 传入的回调函数会再次被执行。
<template>
<div>
<h4>{{ age }}</h4>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const age = ref(28);
// watchEffect: 1.自动收集响应式的依赖 2.默认会先执行一次 3.获取不到新值和旧值
watchEffect(() => {
console.log("age:", age.value); // 监听age的改变
});
const changeAge = () => {
age.value++
}
return {
age,
changeAge
}
}
}
</script>
(2)浏览器中显示的效果下图所示。watchEffect 的回调函数默认先执行一次,打印出 age:28。当单击“修改 age”按钮改变 age 时,watchEffect 会监听到 age 发生变化。此时,watchEffect 的回调函数会再次执行, 并打印出 age:29

3,停止 watchEffect 监听
(1)在某些情况下,我们希望停止监听某个变量的变化。这时可以使用 watchEffect 函数,并接收其返回值的函数,调用该函数即可停止监听。我们对上面样例做个修改,当 age 达到 30 时,停止监听其变化。
<template>
<div>
<h4>{{ age }}</h4>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const age = ref(28);
// stop是watchEffect函数的返回函数,专门用于停止监听
const stop = watchEffect(() => {
console.log("age:", age.value); // 监听age的改变
});
const changeAge = () => {
age.value++
if (age.value > 30) {
stop(); // 停止监听age的变化
}
}
return {
age,
changeAge
}
}
}
</script>
(2)测试一下,watchEffect 的回调函数会默认先执行一次,打印出 age:28。当单击“修改 age” 按钮改变 age 时,如果 age 大于 30,由于调用了 watchEffect 返回的 stop 函数,watchEffect 会取消对 age 变量的监听。

4,清除副作用
(1)watchEffect 函数的参数传入的回调函数可以接收一个 onlnvalidate 函数类型的参数。onlnvalidate 函数的参数也需要接收一个回调函数。当副作用函数再次执行或监听器被停止时,会执行 onlnvalidate 函数传入的回调函数。因此,我们可以在 onInvalidate 函数传入的回调函数中执行一些清除副作用的工作。
- 例如,在实际开发中,我们需要在监听函数中执行网络请求。但是在网络请求还没有完成时,我们就停止了监听器或监听器对应的监听函数被再次执行了。这时,上一次的网络请求应该被取消,即清除该副作用。因此,我们可以借助 onlnvalidate 函数清除该副作用。
(2)下面样例当监听到 age 变化或监听停止时,会执行 onInvalidate 函数中的回调函数。我们可以在该回调函数中清除副作用,例如样例中我们再此清除了上一次的定时器。
<template>
<div>
<h4>{{ age }}</h4>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const age = ref(28);
watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log("网络请求成功~");
}, 2000)
onInvalidate(() => {
// 在这个函数中清除额外的副作用
clearTimeout(timer);
console.log("onInvalidate");
})
console.log("age:", age.value); // 监听age的改变
});
const changeAge = () => age.value++
return {
age,
changeAge
}
}
}
</script>
(3)运行效果如下,刷新页面,立马连续单击 3 次“修改 age”按钮,watchEffect 函数监听到 age 改变了 3 次,并在每次将重新执行 watchEffect 函数的回调函数时,先执行 onInvalidate 函数中的回调函数来清除副作用,即清除了上一次的定时器。 因此,只有最后一次的定时器没有被清除。

附:watchEffect 的执行时机
1,默认执行时机
(1)首先使用 ref 函数定义一个 titleRef 响应式变量,该变量需要在 setup 函数中返回,并绑定到 <h4> 元素的 ref 属性上(注意:不需要用 v-bind 指令来绑定)。当 <h4> 元素挂载完成后,会自动把 DOM 对象赋值到 titleRef 变量上。 为了观察 titleRef 变量被赋值,这里使用 watchEffect 函数监听 titleRef 变量的变化,并打印出来。
提示:使用 Composition API 获取元素或组件的对象非常简单,只需要定义一个前文提到的 ref 对象,然后将该对象绑定到元素或组件的 ref 属性上。
<template>
<div>
<h4 ref="titleRef">hangge.com</h4>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// 1.定义一个titleRef来拿到h4元素的Dom对象(组件对象也是一样)
const titleRef = ref(null);
// 2.h4元素挂载完成之后会自动赋值到titleRef变量上,这里监听titleRef变量被赋值
watchEffect(() => {
console.log(titleRef.value); // 3.打印h4元素的Dom对象
})
return { titleRef }
}
}
</script>
(2)在浏览器中刷新页面,可以看到控制台会打印两次:
- 首先 setup 函数在执行时就会立即执行 watchEffect 传入的副作用函数,即 watchEffect 的回调函数。此时 DOM 并没有挂载,因此打印 null。
- 而当 DOM 挂载时,会为 titleRef 变量赋新的内部值,副作用函数会再次被执行,打印出 <h4> 元素。

2,改变副作用函数的执行时机
(1)我们可以向 watchEffect 函数传递第二个参数,改变副作用函数的执行时机。该参数需要接收一个对象,该对象的 flush 属性用于修改副作用函数的执行时机。
- flush 属性的默认值是 pre,意思是 watchEffect 函数会在元素挂载或更新之前执行。这就解释了前面的例子中,为什么会先打印出一个空元素 null。当依赖的 titleRef 发生改变时,会再次执行一次 watchEffect 函数,打印出该元素。
- 设置 flush:post 的意思是,副作用函数会延迟到组件渲染之后再执行。
- 设置 flush:sync 的意思是依赖变化时同步执行副作用函数。这种执行是低效的, 使用时需谨慎。
提示:在 Vue.js 3.2 以后的版本中,watchPostEffect 是 watchEffect 带有 flush:"post" 选项的别名,watchSyncEffect 是 watchEffect 带有 flush:"sync" 选项的别名。
<template>
<div>
<h4 ref="titleRef">hangge.com</h4>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// 1.定义一个titleRef来拿到h4元素的Dom对象(组件对象也是一样)
const titleRef = ref(null);
// 2.h4元素挂载完成之后会自动赋值到titleRef变量上,这里监听titleRef变量被赋值
watchEffect(() => {
console.log(titleRef.value); // 3.打印h4元素的Dom对象
}, {
flush: "post" // 支持 pre, post, sync
})
return { titleRef }
}
}
</script>
(3)在浏览器中刷新页面,可以看到这次控制台只打印了一次,打印了“<h4>hangge.com</h4>”元素。

全部评论(0)