侦听器
一、 组合式API:
1.1 watch()
函数
- 创建侦听器:
- 语法:
// 先导入 watch 函数 import { watch } from 'vue' watch(source, callback, options)
source
:- 需要侦听的数据源,可以是
ref
(包括计算属性)、一个响应式对象、一个getter函数(获取对象属性的函数)、或多个数据源组成的数组;- 🔺 注意:
- 第一个参数如果是
ref
声明的,不需要添加.value
,watch
会自动读取;
- 第一个参数如果是
- 和选项式API的区别:
- 选项式API此处是个字符串;
- 组合式API此处是个变量 / 数组 / 函数(要侦听的数据源);
- 🔺 注意:
- 需要侦听的数据源,可以是
callback
:- 回调函数;
- 侦听单个数据源:
- 第一个参数 为 新值;
- 第二个参数 为 旧值;
- 侦听多个数据源组成的数组:
- 第一个参数数组 是 新值;
- 第二个参数数组 是 旧值;
optinos
:- 配置项;
deep: true
:- 深度侦听,一般用在侦听的是
getter
函数返回的对象;
- 深度侦听,一般用在侦听的是
immediate: true
:- 创建好侦听器立即执行一遍;
flush: 'post'
:- 更改回调的执行机制(DOM更新后再执行);
- 语法:
- 停止侦听器:
- 调用
watch()
返回的函数 即可 停止对该数据源的侦听;
- 调用
- 总结:
- 侦听原始数据类型:
- 原始数据类型可以直接侦听;
- 侦听的如果是对象的某个属性,
watch()
的第一个参数必须是getter函数
(一个箭头函数,返回值是要侦听的对象属性);
- 侦听对象类型:
- 不开启深度侦听:
watch()
第一个参数就是要侦听的数据,如果想改变某个属性值,可以触发回调,但是新旧值一样;watch()
第一个参数是个getter()
,如果改变某个属性,默认情况下不会触发回调;
- 开启深度侦听 +
getter()
:- 对象的某个属性值改变,会触发回调,但是新旧值一样;
- 🔺
watch()
不管第一个参数是啥,对整个对象重新赋值,都是侦听不到的;
- 不开启深度侦听:
- 侦听由多个响应式数据组成的数组:
- 数组里面有基本数据类型 / 对象的某个属性,可以触发回调,新旧值不一样;
- 有对象,某个属性发生改变,新旧值一样;
- 侦听原始数据类型:
- 示例展示:
- 侦听原始数据类型:
<script setup> import { ref, reactive, watch } from 'vue'; let str = ref('禁止摆烂-才浅') let stopStrWatch = null const info = reactive({ name: '才浅', age: 22 }) let stopInfoWatch = null // TODO 侦听原始数据类型的数据 // NOTE stopStrWatch - watch 函数的返回值,调用该函数可以停止侦听器 stopStrWatch = watch( str, (newVal, oldVal) => { console.log('str') console.log(newVal, oldVal) } ) // TODO 侦听对象的某个属性 - 需要提供 getter 函数(返回对象属性的函数) // NOTE stopInfoWatch - watch 函数的返回值,调用该函数可以停止侦听器 stopInfoWatch = watch( () => info.age, (newVal, oldVal) => { console.log('info.age') console.log(newVal, oldVal) } ); </script> <template> <h4>str</h4> <input type="text" v-model.trim="str"> <hr><br> <h4>info.age</h4> <input type="text" v-model.number.trim="info.age"> </template>
- 效果展示:
- 效果展示:
- 侦听对象类型的数据:
<script setup> import { ref, reactive, watch } from 'vue'; let info = reactive({ name: '才浅', age: 22 }) let likes = reactive({ music: '听音乐', anime: '看动漫' }) let anime = reactive({ one: '完美世界', two: '不良人' }) // TODO 侦听对象 // NOTE 改变数据源的某个属性,可以触发回调,但是 newVal === oldVal => true watch( info, (newVal, oldVal) => { console.log(newVal === oldVal) } ) // TODO 侦听对象 - getter函数(获取对象 / 对象的某个属性的函数) // NOTE 如果改变某个属性值,默认情况下回调不会触发 watch( () => likes, (newVal, oldVal) => { console.log(newVal, oldVal) } ) // TODO 侦听对象 - getter函数 + 配置项 // NOTE 如果嵌套属性值发生变化,并且配置了深度侦听,会触发回调,但 newVal === oldVal watch( () => anime, (newVal, oldVal) => { console.log(newVal, oldVal) }, { // NOTE 开启深度侦听 deep: true } ) </script> <template> <h4>info.age</h4> <input type="text" v-model.number.trim="info.age"> <hr><br> <h4>likes.anime</h4> <input type="text" v-model.trim="likes.anime"> <hr><br> <h4>anime.one</h4> <input type="text" v-model.trim="anime.one"> <hr><br> </template>
- 效果展示:
- 效果展示:
- 侦听多个数据源组成的数组:
<script setup> import { ref, reactive, onMounted, watch } from 'vue'; let str = ref('禁止摆烂-才浅') let likes = reactive({ music: '听音乐', anime: '看动漫' }) let anime = reactive({ one: '完美世界', two: '不良人' }) // TODO 侦听多个数据源组成的数组 // NOTE 都能触发回调 // NOTE 侦听:基本数据类型 - 发生改变,新旧值不一样 // NOTE 侦听:对象的某个属性 - 发生改变,新旧值不一样 // NOTE 侦听:对象数据类型 - 某个属性发生改变,新旧值一样 watch( [str, () => likes.anime, anime], ([newStr, newLikesAnime, newAnime], [oldStr, oldLikesAnime, oldAnime]) => { console.log([newStr, newLikesAnime, newAnime], [oldStr, oldLikesAnime, oldAnime]) } // (newVal, oldVal) => { // console.log(newVal, oldVal) // } // 两种写法打印的数据都一样 ) </script> <template> <h4>侦听 - str</h4> <input type="text" v-model="str"> <hr><br> <h4>侦听 - likes.anime</h4> <input type="text" v-model="likes.anime"> <hr><br> <h4>侦听 - anime</h4> <input type="text" v-model="anime.one"> </template>
- 效果展示:
- 效果展示:
- 侦听原始数据类型:
1.2 watchEffect()
函数
watchEffect()
会立即执行一遍回调函数,如果这时函数产生了副作用,Vue会自动追踪副作用的依赖关系,自动分析出响应源;- 语法:
import { watchEffect } from 'vue' watchEffect(() => {}, {})
- 示例展示:
<script setup> // 引入 watch 函数 import { reactive, ref, watchEffect } from 'vue' // 账号 let account = ref('Abc') // 员工 let emp = reactive({ name: 'Jack', salary: 7000 }) // 创建成功后立即执行一遍 watchEffect(() => { // 此处用到了数据源,如果该数据源的值发生了变化,会重新执行该回调函数 console.log('账号:' + account.value) console.log('员工的薪资:' + emp.salary) }) </script> <template> 账号:<input type="text" v-model="account"> <hr> 员工薪资:<input type="number" v-model="emp.salary"> </template>
- 回调的触发时机:
- 默认情况下,用户创建的侦听器回调,都会在Vue组件更新之前 被调用,这意味着你在侦听器回调中访问的DOM将是被Vue更新之前的状态;
- 如果想在侦听器回调中能访问到被Vue更新之后的DOM,就需要指明
flush: 'post'
选项,或者你也可以使用更方便的别名watchPostEffect()
函数; - 示例展示:
<script setup> import { onMounted, reactive, ref, watchEffect, watchPostEffect } from 'vue' // 账号 let account = ref('Abc') // 密码 let password = ref('123456') // 员工 let emp = reactive({ name: 'Jack', salary: 7000 }) // 当视图渲染成功后 onMounted(() => { // 侦听账号 watchEffect(() => { console.log('-------------------------------------') console.log('账号:' + account.value) // 默认情况下,回调触发机制:在 Dom 更新之前 console.log(document.getElementById('titleAccount').innerHTML) }) // 侦听密码 watchEffect( () => { console.log('=====================================') console.log('密码:' + password.value) // 默认情况下,回调函数触发机制:在 Dom 更新之前 console.log(document.getElementById('titlePassword').innerHTML) }, // 更改回调函数触发机制:在 Dom 更新之后 { flush: 'post' } ) // 侦听薪资 watchPostEffect(() => { console.log('=====================================') console.log('员工薪资:' + emp.salary) // 回调函数的触发机制:在 Dom 更新之后 console.log(document.getElementById('titleSalary').innerHTML) }) }) </script> <template> <h1 id="titleAccount"> 账号: <i>{{ account }}</i> </h1> 账号:<input type="text" v-model="account"> <hr> <h1 id="titlePassword"> 密码: <i>{{ password }}</i> </h1> 密码:<input type="text" v-model="password"> <hr> <h1 id="titleSalary"> 员工薪资: <i>{{ emp.salary }}</i> </h1> 员工薪资:<input type="number" v-model="emp.salary"> </template>
- 效果展示:
- 效果展示:
- 停止侦听器:
- 要手动停止一个侦听器,请调用
watchEffect()
或watchPostEffect()
返回的函数;
- 要手动停止一个侦听器,请调用
二、 选项式API
- 在 选项式API 中,我们可以使用
watch选项
在每次响应式属性发生变化时触发一个函数;
2.1 函数式侦听器
- 在
watch
选项中声明的函数即为函数式侦听器,其中函数名就是要侦听的数据源,函数中的参数1为新值,参数2为旧值;export default { watch: { 侦听的数据(newVal, oldVal) { // 相关逻辑 }, // 侦听对象的某个属性 '对象.属性'(newVal, oldVal) { // 相关逻辑 } } }
- 示例展示:
<script> export default { data: () => ({ str: '禁止摆烂-才浅', info: { name: '才浅', age: 22 } }), // TODO 函数侦听器 - 侦听引用数据类型 / 对象的某个属性 watch: { /** * * @param {*} newVal 新值 * @param {*} oldVal 旧值 */ str(newVal, oldVal) { console.log(newVal, oldVal) }, // TODO 侦听对象的某个属性 'info.age'(newVal, oldVal) { console.log(newVal, oldVal) } } } </script>
2.2 对象式侦听器:
-
在
watch
选项中声明的对象即为对象式侦听器,对象名就是要侦听的数据源,其中对象里面的handler
函数即为数据源发生变化后要执行的函数,其 参数1 为 新值,参数2 为 旧值; -
配置项:
-
1️⃣
deep
:watch
默认是浅层的,被侦听的属性,仅在被赋新值时,才会触发回调函数,而嵌套属性的变化不会触发;- 也就是只有当整个对象被重新赋值的时候会触发,对象中的某个属性值改变不会触发;
- 如果想侦听所有嵌套的变更,就需要深层侦听器
deep: true
选项; - 如果改变侦听数据的某个嵌套数据,
handler
也会触发; - 深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大;
- 注意:
- 如果改变的是整个侦听数据,新值就是当前最新的值,旧值就是改变之前的值;
- 如果改变的是侦听数据的某个属性,新值和旧值是一样的;
-
2️⃣
immediate
:watch
默认是懒执行的,仅当数据变化时,才会执行回调,但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调,可采用immediate: true
选项;
-
3️⃣
flush
:- 默认情况下,用户创建的侦听器回调,都会在Vue组件更新之前被调用,这意味着你在侦听器回调中访问的DOM将是被Vue更新之前的状态;
- 如果想在侦听器回调中能访问被Vue更新之后的DOM,就需要指明
flush: 'post'
选项; - 更改
handler()
的执行机制,DOM更新后执行;
-
-
总结:
- 侦听整个对象:
- 不开启深度侦听:
- 只有当对象被重新赋值的时候触发
handler()
;
- 只有当对象被重新赋值的时候触发
- 开启深度侦听:
- 改变对象某个属性的时候就可以触发;
- 不开启深度侦听:
- 侦听整个对象:
-
使用场景:
- 主要还是用来侦听 - 引用数据类型;
-
语法:
export default { watch: { 侦听的数据: { // deep、immediate、flush配置项根据需要配置,一般都是会加deep的 // 开启深度侦听 deep: true, handler(newVal, oldVal) { // 相关逻辑 } } } }
-
示例展示:
<script> export default { data: () => ({ info: { name: '才浅', age: 22 } }), methods: { changeInfo() { this.info = { name: '张三', age: 30 } } }, // TODO 对象式侦听器 - 侦听引用数据类型 // NOTE 不开启深度侦听,侦听的是整个对象,只有当整个对象改变的时候才会触发handler(也就是给info重新赋值的时候触发handler) // NOTE 开启深度侦听,改变对象的某个属性,可以触发handler,但是 newVal === oldVal => true watch: { info: { // 开启深度侦听 deep: true, handler(newVal, oldVal) { console.log(newVal, oldVal) } } } } </script> <template> <button @click="changeInfo">对info重新赋值</button> <hr><br> <h4>侦听 - info</h4> <input type="number" v-model.trim.number="info.age"> <hr><br> </template>
2.3 this.$watch()
侦听器
- 使用 组件实例 的
$watch()
方法来命令式地创建一个侦听器,它还允许你 提前停止 该 侦听器; - 创建侦听器:
- 语法:
this.$watch(data, method, object)
data
:- 侦听的数据,类型为
String
;
- 侦听的数据,类型为
method
:- 回调函数,参数一为新值,参数二为旧值;
object
:- 配置项;
deep: true ➡ 深度侦听 immediate: true ➡ 创建时立即触发 flush: 'post' ➡ 更改回调机制(DOM更新之后,执行回调)
- 语法:
- 停止侦听器:
this.$watch()
的返回值是个函数,调用该函数就可以停止该数据的侦听器;
- 示例展示:
<script> export default { data: () => ({ str: '禁止摆烂-才浅', info: { name: '才浅' }, student: { age: 22 }, // 调用该函数,可以停止侦听str数据源 stopStrWatch: null, // 调用该函数,可以停止侦听student数据源 stopStudentWatch: null }), methods: { changeStudent() { this.student = { name: '才浅', age: 22 } } }, // NOTE 生命周期函数 mounted() { // TODO 创建侦听器 // TODO 侦听 简单数据类型 this.stopStrWatch = this.$watch('str', (newVal, oldVal) => { console.log(newVal, oldVal) }) // TODO 侦听 对象的某个属性 this.$watch('info.name', (newVal, oldVal) => { console.log(newVal, oldVal) }) // TODO 侦听 引用数据类型 // NOTE 不开启深度侦听 // NOTE 改变对象的某个属性,不会触发回调 // NOTE 对整个对象进行赋值,可以触发,新旧值不一样 // NOTE 开启深度侦听 // NOTE 改变 侦听数据的某个属性,新值 === 旧值 // NOTE 改变 整个侦听数据(对对象重新赋值),新旧值不一样 this.stopStudentWatch = this.$watch('student', (newVal, oldVal) => { console.log(newVal, oldVal) }, { deep: true, }) }, } </script> <template> str:<input type="text" v-model.trim="str"> <button @click="stopStrWatch">停止侦听str</button> <hr> <br> info.name:<input type="text" v-model.trim="info.name"> <hr> <br> <button @click="changeStudent">改变student的值</button> student.age:<input type="text" v-model.umber.trim="student.age"> <button @click="stopStudentWatch">停止侦听student</button> </template>