watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
1:reactive监听对象
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = reactive({
a: 1,
b: 2
})
watch(
//source
() => {
console.log('x')
return sum.a + sum.b
},
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.a++
sum.b--
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
分析:组件一开始挂载先执行一次监听中的source,一秒后,因为对sum.a或sum.b操作,所以又执行了一次source,但是source的返回值都没有发生变化,所以都没有执行回调函数(cb),这样监听只有当sum.a + sum.b返回的值发生变化,才会执行回调函数(cb)
2:reactive监听对象(使用 immediate: true,立即执行一次回调函数(cb))
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = reactive({
a: 1,
b: 2
})
watch(
//source
() => {
console.log('x')
return sum.a + sum.b
},
//cb
(value) => {
console.log('执行回调sum:', value)
},
// 立即执行一次回调函数(cb)
{
immediate: true
}
)
setTimeout(() => {
sum.a++
sum.b--
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
3:reactive监听对象(使用 immediate: true,立即执行一次回调函数(cb))
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = reactive({
a: 1,
b: 2
})
watch(
//source
() => {
console.log('x')
return sum.a + sum.b
},
//cb
(value) => {
console.log('执行回调sum:', value)
},
// 深度监听
{
deep: true
}
)
setTimeout(() => {
sum.a++
sum.b--
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
分析:为什么加了深度监听后会执行回调?如果你添加了 { deep: true } 选项,即使计算值相同,回调也会执行,这是因为:
1:深度监听的机制:deep: true 会让 Vue 不仅比较计算结果的最终值,还会追踪依赖项的变化
2:依赖项变化:虽然 sum.a + sum.b 的结果没变,但 sum.a 和 sum.b 本身都发生了变化
3:触发条件:深度监听下,只要依赖的响应式数据 (这里是 sum.a 和 sum.b) 发生了变化,就会触发回调,不管计算结果是否相同
如果你希望在依赖项变化时总是执行回调,即使计算结果相同,可以使用 watchEffect:
watchEffect(() => {
const value = sum.a + sum.b
console.log('执行回调sum:', value)
})
或者如果你想保持使用 watch 但总是触发,可以添加 flush: ‘sync’ 选项:
watch(
() => sum.a + sum.b,
(value) => {
console.log('执行回调sum:', value)
},
{ deep: true, flush: 'sync' }
)
总结:
默认情况下 watch 只在返回值变化时触发
deep: true 会让它追踪依赖项的变化,即使返回值相同也会触发
4:reactive监听对象
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = reactive({
a: 1,
b: 2,
c: 3
})
watch(
//source
() => {
console.log('x:', sum.c)
return sum.a + sum.b
},
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.c++
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
分析:一开始先执行source方法所以打印了x:3,又因为一秒后修改了c的值,同时source中对c有监听,所以又执行了一次source,但是因为返回的都是a+b,返回值没有变化,所以不执行回调函数(cb)
5:reactive监听对象
watch(
//source
() => {
console.log('x:', sum.c = 10) //修改了 `sum.c`,但没有 **读取** 它(不是依赖)
return sum.a // 只有 `sum.a` 被 **读取**,所以 Vue 只监听 `sum.a` 的变化
},
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.c++
console.log('执行定时器了')
}, 1000)
执行结果如下
5-1:reactive监听对象
watch(
//source
() => {
console.log('x:', sum.c === 10) //读取 `sum.c`, **读取** 它(是依赖)
return sum.a // 只有 `sum.a` 被 **读取**,所以 Vue 只监听 `sum.a` 的变化
},
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.c++
console.log('执行定时器了')
}, 1000
执行结果如下
总结:如果 getter 函数内部有代码修改了响应式数据(如 sum.c = 10),但 没有读取它,则 Vue 不会 将其视为依赖,因此不会触发重新执行,(sum.c === 10),读取它,则 Vue 会 将其视为依赖,会触发重新执行
6:在 Vue 3 的 watch API 中,reactive的对象,使用函数返回值 () => sum 和直接使用 sum 作为侦听源(source)有重要区别
6-1:reactive的对象执行结果
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = reactive({
person: { age: 18, name: '张三' },
b: 2,
c: 3
})
watch(
// source
() => {
console.log('x')
return sum
},
// sum,
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.person.name = '李四'
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
6-2:reactive的对象执行结果
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = reactive({
person: { age: 18, name: '张三' },
b: 2,
c: 3
})
watch(
// source
// () => {
// console.log('x')
// return sum
// },
sum,
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.person.name = '李四'
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
总结区别:
方式 | 监听目标 | 深度监听 | 适合场景 |
---|---|---|---|
watch(sum, cb) | 整个响应式对象 | 自动开启 | 需要深度监听所有变化 |
watch(() => sum, cb) | 对象的引用 | 不开启 | 几乎无用(reactive 引用不变) |
watch(() => sum.xxx, cb) | 特定属性 | 不开启 | 精确监听特定属性 |
7:在 Vue 3 的 watch API 中,ref的对象,使用函数返回值 () => sum 和直接使用 sum 作为侦听源(source)有重要区别
7-1:ref的对象执行结果
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = ref({
person: { age: 18, name: '张三' },
b: 2,
c: 3
})
watch(
// source
// () => {
// console.log('x')
// return sum.value
// },
sum,
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.value.person.name = '李四'
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
7-2:ref的对象执行结果
<template>
<div>
<h1>情况二:watchEffect自动监视数据</h1>
<h2>两个数字的和:{{ count }}</h2>
</div>
</template>
<script setup lang="ts" name="DemoWatch">
import { reactive, ref, watch } from 'vue'
let count = ref(0)
const sum = ref({
person: { age: 18, name: '张三' },
b: 2,
c: 3
})
watch(
// source
() => {
console.log('x')
return sum.value
},
// sum,
//cb
(value) => {
console.log('执行回调sum:', value)
}
)
setTimeout(() => {
sum.value.person.name = '李四'
console.log('执行定时器了')
}, 1000)
</script>
<style scoped></style>
执行结果如下
总结区别:
监听方式 | 触发条件 | 深度监听 | 适用场景 |
---|---|---|---|
watch(sum, cb) | 仅 sum.value = newObj 时触发 | ❌ | 监听整个 ref 替换 |
watch(sum.value, cb)同理成reactive | 深度监听嵌套变化(不推荐) | ✅ | 不推荐(可能丢失响应性) |
watch(() => sum.value, cb, { deep: true }) | 深度监听嵌套变化 | ✅ | 推荐(清晰且可控) |
watch(() => sum.value.xxx, cb) | 仅监听特定属性 | ❌ | 精确监听,性能优化 |