watchEffect并没有要求你声明被监听的变量,而是,你在执行体里写哪个变量,Vue就收集、监听哪个变量,而且可以同时监听多个变量
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0 组件初始化的时候就会执行一次
setTimeout(() => {
count.value++
// -> logs 1 依赖发生变化在执行
}, 100)
第一点我们可以从示例代码中看到 watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)。
第二点就是 watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的。
第三点是 watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。
在watchEffect里操作响应式数据,不会引起无限循环监听,这虽然很显而易见,但是也在此说一句。
多个watchEffect的执行顺序是watchEffect的书写顺序。
watchEffect是立即执行的,所以组件初始化的时候就全部执行了一遍
watchEffect(() => {
console.log('b:', s.value.b);
});
watchEffect(() => {
console.log('a - b:', s.value.a + '-' + s.value.b);
});
watchEffect(() => {
console.log('value:', s.value);
});
自动停止
先说watchEffect生命周期的开始,是从组件的setup()函数或生命周期钩子被调用时开始。自动停止是在组件卸载时自动停止。
手动停止
将watchEffect赋值给变量,执行这个变量即可手动停止。比如:
const xx = watchEffect(() => {
console.log('a:', s.value.a);
s.value.a += 10
});
// 后来某个时间执行了:
xx(); // 停止监听
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
副作用主要有:DOM更新、watchEffect、watch、computed、、、、
你没看错,既然更新视图层才是主作用,那么视图层更新到DOM上在Vue眼里是副作用,而且,变更响应式数据触发执行computed和触发执行watchEffect当然也是副作用。所以watchEffect本身就是副作用。
onInvalidate(fn)传入的回调会在 watchEffect 重新运行或者 watchEffect 停止的时候执行
watchEffect(() => {
// 异步api调用,返回一个操作对象
const apiCall = someAsyncMethod(props.userID)
onInvalidate(() => {
// 取消异步api的调用。
apiCall.cancel()
})
})
清除副作用是什么意思
例子
比如你有一个页码组件,里面有5个页码,点击就会异步请求数据。于是我就做了一个监听,监听当前页码,只要有变化就ajax一次。
现在问题是,如果我点击的比较快,从1到5全点了一遍,那么会有5个ajax请求,最终页面会显示第几页的内容?你说第5页?那你是假定请求第5页的ajax响应的最晚,事实呢?并不一定。于是这就会导致错乱。还有一个问题,我连续快速点5次页码,等于我并不想看前4页的内容,那么是不是前4次的请求都属于带宽浪费?这也不好。于是官方就给出了一种解决办法:
可用防抖或者节流
或者用watchEffect提供取消副作用的接口,也就是onInvalidate方法。Invalidate中文译义是作废,onInvalidate也就是作废监听器。
import{
watchEffect,
ref,
createApp,
} from "vue"
// 查询数据
function fetchData(pageSize, limit){
return new Promise(resolve => {
// 模拟查询数据需要的时间
setTimeout(() => {
resolve(`查询第 ${pageSize} 页, 每页数量 ${limit}`)
}, 3000)
})
}
let app = createApp({
setup(){
let pageSize = ref(1);
let limit = ref(10);
let list = ref(null);
watchEffect(async (onInvalidate) => {
let cancel = false;
onInvalidate(() => {
cancel = true;
})
let result = await fetchData(pageSize.value, limit.value)
// 已经失效就不更新
if(cancel) return ;
list.value = result;
})
return {
pageSize,
limit,
list,
}
},
注意: 如副作用中设计到DOM的操作和ref的获取,watchEffect需要放到mounted周期中执行
onMounted(() => {
watchEffect(() => { // … 操作dom或ref等 })
})
Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick”中多个状态改变导致的不必要的重复调用
同一个“tick”的意思是,Vue的内部机制会以最科学的计算规则将视图刷新请求合并成一个一个的"tick",每个“tick”刷新一次视图,比如a=1;b=2;只会触发一次视图刷新。$nextTick的Tick就是指这个。
//如:
import {watchEffect,ref} from 'vue'
setup(){
const x=ref(null) //将这个ref绑定到指定标签或组件上
watchEffect(()=>{
console.log(x)
})
return {
x
}
}
watchEffect(()=>{
console.log(x)
},{
flush:'post' //在组件更新完成之后操作
})
Vue 2使用this.$nextTick()去获取组件更新完成之后的DOM,和上面是一个意思。
options
{ flush: “pre” | “post” | “sync”}
选项参数可以决定handler执行的时机
pre 在组件更新之前操作
post 在组件更新完成之后操作
sync 同步操作
computed和watchEffect相同的地方是会自动收集依赖,在值更新时会触发回调,会初始化调用一次。但是在触发初始化的时机是不一样的 ,如果computed的值没有被使用,是不会触发回调的,只有在该值被使用的时候才会触发回调,但watchEffect是在setup的时候就会初始化。
官方说,watch也有停止侦听,清除副作用、副作用刷新时机和侦听器调试行为
Vue 3跟Vue 2的computed的差别在于,Vue 2是所有计算属性都是根对象的属性,Vue 3是计算属性都是独立变量
参考1