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.bconsole.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) | 僅監聽特定屬性 | ? | 精確監聽,性能優化 |