先写一个vue3的完整示例,取名为home.vue
javascript
<script setup>
import { ref,watchEffect,onMounted,onUnmounted} from 'vue'
const count=ref(0);
const userid=ref(1);
const userData=ref(null);
const timer=ref(0);
//同步监听,只是读取数据或是计算属性没有必要进行清理
watchEffect(()=>{
console.log(`当前count:${count.value},userid:${userid.value}`);
})
//异步获取数据,如获取后端数据,或是有定时器之类的setTimeout(),便要做清理
watchEffect(async (onInvalidate)=>{
const id=userid.value;
console.log(`开始获取用户${id}的数据...`);
//模拟异步请求
const controller=new AbortController(); //创建一个"取消控制器"
try{
const timeoutid=setTimeout(()=>{
userData.value={id,username:`用户${id}`}
console.log(`用户${id}数据获取完成`)
},1000);
//清理函数:在effect重新执行前或停止时调用
onInvalidate(()=>{
clearTimeout(timeoutid);
controller.abort() //如果是fetch获取后端数据或定时器,立刻中断
console.log(`清理用户${id}的pending请求`)
})
}catch(e)
{
if(e.name==='AbortError')
{
console.log('请示被取消');
}
}
})
//手动停止watchEffect
let stopTimerWatch;
onMounted(()=>{
stopTimerWatch=watchEffect(()=>{
console.log(`计时器:${timer.value}`)
})
})
onUnmounted(() => {
// 组件卸载时手动停止,防止内存泄漏
stopTimerWatch && stopTimerWatch()
console.log('timer 的 watchEffect 已停止')
})
//辅助函数
const increment=()=>count.value++;
const changeuser=()=>userid.value++;
const startTimer=()=>{
const interval = setInterval(() => timer.value++, 1000)
// 5秒后停止 timer 的 watchEffect
setTimeout(() => {
stopTimerWatch && stopTimerWatch()
clearInterval(interval)
console.log('手动停止了 timer 监听')
}, 5000)
}
</script>
<template>
<div>
<h4>count:{{count}}</h4>
<button @click="increment">+1</button>
<h4>用户ID:{{userid}}</h4>
<button @click="changeuser">切换用户</button>
<p>用户数据:{{userData}}</p>
<h4>计时器:{{timer}}</h4>
<button @click="startTimer">开始计时(5秒后自动停止监听)</button>
</div>
</template>
<style scoped>
</style>
不需要清理的情况
//只是纯计算/读取,没有遗留任何"副作用",所以不需要清理
javascript
import { ref, watchEffect } from 'vue'
const count = ref(0)
const double = ref(0)
// 纯同步:只是读取 count,计算 double
// 没有产生"副作用"(定时器、请求、事件等),所以不需要 onInvalidate
watchEffect(() => {
double.value = count.value * 2
console.log('count 变了,double =', double.value)
})
异步需要清理的情况
javascript
import { ref, watchEffect } from 'vue'
const keyword = ref('vue')
const results = ref([])
watchEffect(async (onInvalidate) => {
const currentKeyword = keyword.value
// 产生副作用:发起网络请求
const controller = new AbortController()
try {
const res = await fetch(`/api/search?q=${currentKeyword}`, {
signal: controller.signal
})
const data = await res.json()
// ⚠️ 竞态问题:如果 keyword 快速变化,旧的请求可能后返回
// 需要确保只采用最新请求的结果
if (currentKeyword === keyword.value) {
results.value = data
}
} catch (e) {
// 忽略取消请求的错误
}
// 🔧 清理:在 effect 重新执行前,取消上一个未完成的请求
onInvalidate(() => {
controller.abort()
})
})
同步也需要清理的情况
javascript
import { ref, watchEffect } from 'vue'
const visible = ref(false)
watchEffect((onInvalidate) => {
if (visible.value) {
// 同步产生了副作用:添加事件监听
const handler = (e) => console.log('点击了', e.target)
document.addEventListener('click', handler)
// 🔧 必须清理,否则监听会越积越多
onInvalidate(() => {
document.removeEventListener('click', handler)
})
}
})
终止stop使用时机
javascript
import { ref, watchEffect, onUnmounted } from 'vue'
const count = ref(0)
const stop = watchEffect(() => {
console.log(count.value)
})
// 情况1:组件卸载时停止,防止内存泄漏
onUnmounted(() => {
stop()
})
// 情况2:满足某个条件后不再需要监听
if (someCondition) {
stop()
}