侦听数据的变化
在前面的Options API中,我们可以通过watch选项
来侦听data
或者props
的数据变化,当数据变化时执行某一些操作。
在Composition API中,我们可以使用watchEffect
和watch
来完成响应式数据的侦听
;
- watchEffect用于自动收集响应式数据的依赖;
- watch需要手动指定侦听的数据源;
watchEffect
当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect
。
- 首先,watchEffect传入的函数在
页面初次渲染
的时候会被立即执行一次
,并且在执行函数的过程中会收集依赖
,这些依赖为响应式对象
; - 其次,只要收集的依赖中不管哪个发生变化,watchEffect传入的函数都会再次执行;
watchEffect的基本使用
watchEffect的回调参数会在页面首页渲染的时候执行一次,通过这次执行,收集回调中依赖的响应式变量,然后监听其变化,一但侦听到变量值的改变,就会重新执行其回调函数
例:
<template>
<div>
<h1>名字: {{ name }} - 年纪: {{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年纪</button>
</div>
</template>
<script>
import { ref, watchEffect } from "vue";
export default {
setup() {
const name = ref("why");
const age = ref(18);
//watchEffect的回调参数会在页面首页渲染的时候执行一次,通过这次执行,收集回调中依赖的响应式变量,然后监听其变化,
//一但侦听到变量值的改变,就会重新执行其回调函数
watchEffect(() => {
//此函数中依赖的响应式对象为name和age,所以,不管name和age哪个的值发生改变,此回调函数都会重新执行
console.log("name-", name.value, "age-", age.value);
});
const changeName = () => (name.value = "coder");
const changeAge = () => age.value++;
return {
name,
age,
changeName,
changeAge,
};
},
};
</script>
<style lang="scss" scoped></style>
watchEffect的停止侦听
watchEffect函数的返回值也是一个函数,调用此函数会停止侦听
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可。
案例:当age达到20的时候就停止侦听
<template>
<div>
<h1>名字: {{ name }} - 年纪: {{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年纪</button>
</div>
</template>
<script>
import { ref, watchEffect } from "vue";
export default {
setup() {
const name = ref("why");
const age = ref(18);
//watchEffect函数会返回一个函数,调用此函数会停止侦听
const stop = watchEffect(() => {
console.log("age-", age.value);
});
const changeName = () => (name.value = "coder");
const changeAge = () => {
if(age.value > 20) {
stop() //当age大于20时,停止侦听
}
age.value++
};
return {
name,
age,
changeName,
changeAge,
};
},
};
</script>
<style lang="scss" scoped></style>
watchEffect清除副作用
什么是清除副作用呢?
- 比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
- 那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
onInvalidate也是一个函数
,接收一个回调函数
作为参数
- 当副作用即将
重新执行
或者侦听器被停止
时会执行onInvalidate函数传入的回调函数
; - 我们可以在传入的回调函数中,执行一些清除工作;
- 当watchEffect依赖的响应式对象的值改变时,执行其回调函数前,会先执行回调接受的onInvadiate函数
<template>
<div>
<h1>名字: {{ name }} - 年纪: {{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年纪</button>
</div>
</template>
<script>
import { ref, watchEffect } from "vue";
export default {
setup() {
const name = ref("why");
const age = ref(18);
let timer = null;
//watchEffect的回调函数参数在执行的时候也接收一个onInvalidate函数,
//onInvalidate函数也接收一个回调函数作为参数
//可以在onInvadiate函数的回调函数中做一些取消的处理,比如取消请求
//当watchEffect侦听的变量值改变时,执行其回调函数前,会先执行回调接受的onInvadiate函数
watchEffect((onInvadiate) => {
timer = setTimeout(() => {
console.log("模拟发送请求,携参:name,age");
}, 3000);
console.log("name-", name.value, "age-", age.value);
onInvadiate(() => {
//不管onInvadiate在watchEffect回到函数中的位置,只要回调函数执行,都优先执行onInvadiate
//取消上次发送的请求
clearTimeout(timer);
console.log("onInvalidate");
});
});
const changeName = () => (name.value = "coder");
const changeAge = () => age.value++;
return {
name,
age,
changeName,
changeAge,
};
},
};
</script>
<style lang="scss" scoped></style>

setup中使用ref
在讲解 watchEffect执行时机之前,我们先补充一个知识:在setup中如何使用ref或者元素或者组件?
- 其实非常简单,我们只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可;
<template>
<div>
<h1 ref="h1">hello world</h1>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
setup() {
const h1 = ref(null)
return {
h1
}
}
}
</script>
watchEffect的执行时机
默认情况下,组件的更新会在副作用函数执行之后:
例:
默认情况下watchEffect会在组件挂载之前执行,所以watchEffect的回调会执行两次:
- 第一次执行时,组件还没挂载,h1.value为null
- 组加挂载成功后,h1.value的值变为h1Dom元素,watchEffect会再次执行,因为watchEffect依赖h1这个响应式对象
<template>
<div>
<h1 ref="h1">hello world</h1>
</div>
</template>
<script>
import { ref, watchEffect } from "vue";
export default {
setup() {
let h1 = ref(null);
//默认情况下watchEffect会在组件挂载之前执行,
//所以watchEffect的回调会执行两次:
//第一次执行时,组件还没挂载,h1.value为null
//组加挂载成功后,h1.value的值变为h1Dom元素,watchEffect会再次执行,
//因为watchEffect依赖h1这个响应式对象
watchEffect(() => {
//h1为h1DOM元素
console.log("h1", h1.value);
});
return {
h1,
};
},
};
</script>
<style lang="scss" scoped></style>
我们会发现打印结果打印了两次:
- 这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null;
-
而当DOM挂载时,会给title的h1对象赋值新的值,副作用函数会再次执行,打印出来对应的元素;
image.png
调整watchEffect的执行时机
如果我们希望在第一次的时候就打印出来对应的元素呢?
- 这个时候我们需要改变副作用函数的执行时机;
watchEffect函数接收第二个参数
为一个对象
,此对象中可以做一些配置
-
flush
选项的默认值是pre
,它决定watchEffect的回调函数会在元素挂载
或者更新
之前执行;- 所以我们会先打印出来一个空的,当依赖的title发生改变时,就会再次执行一次,打印出元素;
-
flush: "post"
调整watchEffect的执行时机为元素挂载/更新之后执行 - flush 选项还接受
sync
,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。
例:
<template>
<div>
<h1 ref="h1">hello world</h1>
</div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
setup() {
let h1 = ref(null)
watchEffect(() => {
//h1为h1DOM元素
console.log('h1', h1.value)
}, {
flush: 'post'//调整watchEffect的执行时机为元素挂载/更新之后执行
//默认为'pre' 元素挂载/更新之前执行
})
return {
h1
}
}
}
</script>
<style lang="scss" scoped>
</style>
我们会发现第一次就获取到了元素
因为watchEffect的回调函数是在元素挂载之后才执行的,此时h1.value已经是h1dom元素了

此文档主要内容来源于王红元老师的vue3+ts视频教程
网友评论