返回 导航

Vue.js

hangge.com

Vue.js 3 - Composition API使用详解6(watchEffect函数、监听数据变化)

作者:hangge | 2026-04-30 09:24
    我们知道使用 Options API 能够监听 datapropscomputed 数据的变化,比如当数据变化时执行某些操作。而在 Composition API 中,我们可以使用 watchEffectwatch 函数完成响应式数据的监听。其中,watchEffect 函数用于自动收集响应式数据的依赖,而 watch 函数需要手动指定监听的数据源。 本文我先介绍 watchEffect 函数的使用。

六、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 以后的版本中,watchPostEffectwatchEffect 带有 flush:"post" 选项的别名,watchSyncEffectwatchEffect 带有 flush:"sync" 选项的别名。 

(2)下面样例通过传递 flush:"post" 对象参数将副作用函数的执行时机延迟到组件渲染之后再执行:
<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)

回到顶部