『引言』
在日常开发中,慢慢地就会发现遇到的需求总是多变的,如果要实现下面这个需求会怎么做呢?
🙋🙋♂️提问 🚩:要实现第一次渲染页面的时候,watch
里的回调函数就执行一遍,怎么做❓🤔🤔
回答 📒:使用watch
实现,就是把回调函数直接提取出来,进入页面执行一遍,这样操作比较繁琐。其它方法的话,可以使用今日的主角watchEffect
。
watchEffect
相当于将 watch
的依赖源和回调函数合并,它会立即执行传入的一个函数,自动收集其依赖的函数,监视响应式数据的变化,每次变化都会触发回调函数的重新执行。
watchEffect
监听是立即执行的,会默认初始时就会执行第一次
, 从而可以收集需要监听的数据。
『watchEffect()』
『定义』
【官方解释】
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
-
详细信息
第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
默认情况下,侦听器将在组件渲染之前执行。设置
flush: 'post'
将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置flush: 'sync'
来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。返回值是一个用来停止该副作用的函数。
【我的理解】
watchEffect
可以看成是 watch
和 side effct
,那么就可以理解成,在观察『watch
』到变化后执行一些操作『side effct
』。
『用法』
watchEffect(effect,options)
『watchEffect参数说明』
-
第一个参数:(必传)effect 函数,收集依赖,在组件初始化时立即执行一次。
并且 effect 函数可以接受一个 onInvalidate 函数参数,该参数执行并传入一个 callback ,每次监听回调执行前都会执行该 callback。
-
第二个参数对象(非必传): flush 、 onTrack 、 onTrigger。『onTrack 、 onTrigger』只能在开发模式下工作。
- flush: 跟 watch() 的flush 完全一致,『 'pre' | 'post' | 'sync' 』默认:'pre'。
- onTrack:跟踪之前会触发该函数,收集了多少个依赖就触发多少次,返回对应依赖信息。
- onTrigger:依赖更改就触发执行,同步的,没有缓冲回调。
『官网示例🌰』
『注意』
watchEffect
不会返回一个停止副作用的函数。如果需要停止副作用函数的执行,需要使用watch
API,并通过返回的停止函数来实现。- 使用时不需要具体指定监听的谁,回调函数内直接使用就可以。
- 只能访问当前最新的值,访问不到修改之前的值。
『示例🌰:watchEffect 监听数据』
xml
<template>
<div>
<h1>人物简介</h1>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}岁</p>
<p>爱好:{{info.hobbies.join('、')}}</p>
<p>地址:{{info.address.provice}} - {{info.address.city}}</p>
<p>描述:{{info.description}}</p>
<button @click="modifyInfo">
修改数据
</button>
</div>
</template>
<script setup>
import { ref, reactive, watchEffect } from 'vue'
const name = ref('pupu')
const age = ref(10)
const info = reactive({
hobbies: ['唱歌','画画'],
address: {
provice: '浙江省',
city: '杭州市'
},
description: '一点也不可爱,不喜欢吃蜂蜜!',
})
const modifyInfo = () => {
name.value = 'wnxx'
age.value = 3
info.hobbies = ['打羽毛球','旅游']
info.address.provice = '云南省'
info.address.city = '丽江市'
info.description = '非常的可爱,特别喜欢吃蜂蜜!'
}
// 情况1: 监听ref定义的数据
watchEffect(() => {
console.log(name.value, 'name')
})
// 情况2: 监听reactive定义的数据
watchEffect(() => {
console.log(info.address.provice, info.address.city, 'address')
})
// 情况3: 监听reactive定义的对象数据
watchEffect(() => {
console.log(info, 'info对象')
})
// 情况4: watchEffect什么时候执行
watchEffect(() => {
console.log("执行了")
})
</script>
『效果展示』
『代码解析』
上述示例代码中,分别介绍了几种情况。
-
情况1: 监听ref定义的数据,这种情况下监听的是
『name属性』
,在回调函数中打印了一下『name』
的值,可以在控制台中看到,第二次点击修改按钮,『watchEffect』
触发了。 -
情况2: 监听reactive定义的数据,这时监听的是
『info.address.provice和info.address.city』
,同样的在回调函数中打印一下,可以在控制台中看到,第二次点击修改按钮,『watchEffect』
也触发了。 -
情况3: 监听reactive定义的对象数据,这时监听的是
『info对象』
,也在回调函数中打印了一下,可以看到第一次立即执行时执行了,第二次点击修改按钮后,改变『info』
的『watchEffect』
没触发。这是因为
watchEffect
只会跟踪回调函数中使用的属性,不会递归跟踪对象中所有的属性。 -
情况4: watchEffect什么时候执行,这种情况可以看到第一次立即执行时执行了,第二次并没有执行,控制台没有打印任何信息。
这是因为
『watchEffect』
使用时不需要具体指定监听的谁,回调函数内直接使用就可以,那也就是回调函数中使用了就执行,没使用就不会执行。
『示例🌰:watchEffect的副作用』
xml
<template>
<div>
<h1>人物简介</h1>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}岁</p>
<p>爱好:{{info.hobbies.join('、')}}</p>
<p>地址:{{info.address.provice}} - {{info.address.city}}</p>
<p>描述:{{info.description}}</p>
<button @click="modifyInfo">
修改数据
</button>
</div>
</template>
<script setup>
import { ref, reactive, watchEffect } from 'vue'
const name = ref('pupu')
const age = ref(10)
const info = reactive({
hobbies: ['唱歌','画画'],
address: {
provice: '浙江省',
city: '杭州市'
},
description: '一点也不可爱,不喜欢吃蜂蜜!',
})
const modifyInfo = () => {
name.value = 'wnxx'
age.value = 3
info.hobbies = ['打羽毛球','旅游']
info.address.provice = '云南省'
info.address.city = '丽江市'
info.description = '非常的可爱,特别喜欢吃蜂蜜!'
}
watchEffect(
(onInvalidate) => {
console.log(name.value, 'name')
onInvalidate(()=> {
console.log('执行了onInvalidate')
})
})
</script>
『效果展示』
『代码解析』
因为watchEffect
就是侦听到依赖的变化后执行某些操作,所以watchEffect
的回调函数就是一个副作用函数。
watchEffect
侦听副作用传入的函数可以接收一个 onInvalidate
函数作为入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时(即依赖的值改变)
- 侦听器被停止 (通过显示调用返回值停止侦听,或组件被卸载时隐式调用了停止侦听)
在上述代码中,定义了一些数据、点击按钮修改信息、对『name』
数据进行监听。在对『name』
数据进行监听时,初始化时会在控制台打印『name』
的值为『pupu
』,由于点击按钮会修改『name』
的值,对『name』
的值进行了更新,所以副作用就会重新执行,此时onInvalidate
的回调函数会被触发,控制台就打印了执行了onInvalidate
,之后会执行副作用函数,打印『name』
的值为『wnxx
』。
『示例🌰:关闭 watchEffect 监听』
xml
<template>
<div>
<h1>人物简介</h1>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}岁</p>
<p>爱好:{{info.hobbies.join('、')}}</p>
<p>地址:{{info.address.provice}} - {{info.address.city}}</p>
<p>描述:{{info.description}}</p>
</div>
</template>
<script setup>
import { ref, reactive, watchEffect } from 'vue'
const name = ref('pupu')
const age = ref(10)
const info = reactive({
hobbies: ['唱歌','画画'],
address: {
provice: '浙江省',
city: '杭州市'
},
description: '一点也不可爱,不喜欢吃蜂蜜!',
})
const modifyInfo = () => {
name.value = 'wnxx'
age.value = 3
info.hobbies = ['打羽毛球','旅游']
info.address.provice = '云南省'
info.address.city = '丽江市'
info.description = '非常的可爱,特别喜欢吃蜂蜜!'
}
const unwatchEffect = watchEffect((onInvalidate) => {
console.log(name.value, 'name')
onInvalidate(()=> {
console.log('执行了onInvalidate')
})
})
setTimeout(()=> {
modifyInfo()
unwatchEffect()
}, 1000)
</script>
『效果展示』
『代码解析』
关闭watchEffect
监听,其实非常简单,上述代码中创建一个 watchEffect
监听器,只需要将调用一下就关了。
还有就是在上述代码中,关闭watchEffect
监听,执行unwatchEffect
函数停止监听,就会触发onInvalidate
的回调函数。
『watchPostEffect()』
『定义』
【官方解释】watchEffect()
使用 flush: 'post'
选项时的别名。
『注意』
现在有这样一个疑问,也是通过一个示例进行解答,到底是什么疑问,一起看看下面👇。
提问🚩:在监听器的回调函数中获取DOM,这个时候的DOM是更新前的还是更新后的❓🤔🤔
『示例🌰』
xml
<template>
<div>
<h1>人物简介</h1>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}岁</p>
<p>爱好:{{info.hobbies.join('、')}}</p>
<p>地址:{{info.address.provice}} - {{info.address.city}}</p>
<p>描述:{{info.description}}</p>
<button @click="modifyInfo">
修改数据
</button>
</div>
</template>
<script setup>
import { ref, reactive, watchEffect, watchPostEffect } from 'vue'
const name = ref()
const age = ref(10)
const info = reactive({
hobbies: ['唱歌','画画'],
address: {
provice: '浙江省',
city: '杭州市'
},
description: '一点也不可爱,不喜欢吃蜂蜜!',
})
const modifyInfo = () => {
name.value = 'wnxx'
age.value = 3
info.hobbies = ['打羽毛球','旅游']
info.address.provice = '云南省'
info.address.city = '丽江市'
info.description = '非常的可爱,特别喜欢吃蜂蜜!'
}
// 情况1
watchEffect(
() => {
console.log(name.value, 'name')
})
// 解决办法1
watchEffect(
()=>{
console.log(name.value, '更新后的DOM节点name1')
},
{
flush: 'post'
})
// 解决办法2
watchPostEffect(() => {
console.log(name.value, '更新后的DOM节点name2')
})
</script>
『效果展示』
『代码解析』
上述代码中,监听的是『name
』的值,当监听『name
』的值改变的时候,『vue组件会更新、监听器会回调』
。
一般监听器的回调
会比vue组件的更新
先一步调用。所以在监听器回调中,访问的DOM节点是未更新的。
当想要在监听器回调中访问更新后的DOM节点时,可以传递一个flush: 'post'
参数,或者使用更简便的api watchPostEffect()
。
『watchSyncEffect()』
『定义』
【官方解释】 watchEffect()
使用 flush: 'sync'
选项时的别名。
在组件更新之前或更新之前,立即同步调用它。通常,它在onBeforeUpdate之前调用。
『watch 与 watchEffect 的区别』
不同点:
- 『watch』
- 页面第一次加载时不触发watch函数。
- 可以以数组的形式监听多个参数,如果多个数据同时发生改变,watch只触发一次。
- 可以获取监听的数据的新值和旧值。
- 『watchEffect』
- 页面第一次加载时就会触发 watchEffect 函数。
- 可以监听多个参数,不能监听对象。
- 只可以获取监听的数据的新值。