一文讲清楚vue3中watch的使用

本文详细解释了如何在Vue3中使用watch函数监听不同类型的响应式数据源,包括ref包装的对象、数组,以及reactive对象,讨论了深度侦听、immediate和flush选项的应用。还介绍了如何停止侦听特定数据源。
摘要由CSDN通过智能技术生成

前言

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组

侦听一个ref的数据值

这里先提一下,对于ref函数返回的响应式数据对象,它在模板里面使用时是不需要.value的

import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{counter}}</div>
    <button v-on:click="counter++">+1</button>
`

export default{
    setup: function () {
        let counter = ref(0)

        watch(counter, (newValue, oldValue) =>{
            console.log(newValue, oldValue)
        })

        return {counter}
    },
    template,
}

总结:单个数据值在watch里就直接传一个ref包装后的响应式对象就好啦

侦听多个ref的数据值

我们可以通过watch侦听多个数据源的变化。

如果在同一个函数里同时改变这些被侦听的来源,侦听器只会执行一次。

import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{name}}{{age}}</div>
    <button v-on:click="change">改变姓名年龄</button>
`

export default {
    setup: function () {
        let name = ref('李四')
        let age = ref(22)

        let change = () => {
            name.value += '~'
            age.value++
        }

        watch([name, age], ([newName, newAge], [preName, preAge]) => {
            console.log('姓名变化了', newName, preName)
            console.log('年龄变化了', newAge, preAge)
        })

        return { name, age, change }
    },
    template
}

若要使侦听器执行多次,我们可以利用 nextTick ,等待侦听器在下一步改变之前运行。

import { nextTick } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let change = async () => {
    name.value += '~'
    await nextTick()
    age.value++
}

侦听一个ref的对象

无多层嵌套的对象

import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = ` 
    <div>{{person.name}}</div>
    <div>{{person.age}}</div>
    <button @click="person.age += 1">年龄加一</button>
`

export default {
    setup: function () {
        let person = ref({
            name: '张三',
            age: 18
        })

        // 这个侦听器无效,即控制台无输出
        watch(person, (newVal, oldVal) =>{
            console.log('侦听器1:',newVal, oldVal)
        })

        // getter函数形式,新旧值不一样
        watch(()=> ({...person.value}), (newVal, oldVal) =>{
            console.log('侦听器2:',newVal, oldVal)
        })

        return { person }
    },
    template
}

总结:无多层嵌套的对象不需要开启深度侦听,直接传一个ref响应式对象无效,需要传一个getter函数

多层嵌套的对象

import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{person.name}}</div>
    <div>{{person.age}}</div>
    <div>{{person.scores}}</div>
    <button @click="person.scores.english+=1">英语分数加一</button>
`

export default {
    setup: function () {
        let person = ref({
            name: '张三',
            age: 18,
            scores: {
                math: 100,
                chinese: 90,
                english: 80
            }
        })

        // 这个watch无效,即控制台没任何输出
        watch(person, (newValue, oldValue) => {
            console.log('第一个侦听器:person变化了', newValue, oldValue)
        })

         // 开启深度侦听,新旧值一样
        watch(person, (newValue, oldValue) => {
            console.log('第二个侦听器:person变化了', newValue, oldValue)
        }, { deep: true })

        // 采用函数形式,这个watch无效,即控制台没任何输出
        watch(() => ({ ...person.value }), (newValue, oldValue) => {
            console.log('第三个侦听器:person变化了', newValue, oldValue)
        })

        // 采用函数形式,开深度侦听,新旧值一样
        watch(() => ({ ...person.value }), (newValue, oldValue) => {
            console.log('第四个侦听器:person变化了', newValue, oldValue)
        }, { deep: true })

        return { person }
    },
    template
}

总结:

  1. 没有开启深度侦听时,无论是直接传一个ref响应式对象,还是一个getter函数,watch皆无效;

  2. 开启深度侦听时,无论是直接传一个ref响应式对象,还是一个getter函数,新旧值一样

侦听一个ref的数组

import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{arr1}}</div>
    <button v-on:click='changeArr1'>改为8</button>
`

export default {
    setup: function () {
        let arr1 = ref([1, 2, 3])

        // 这个watch无效,即控制台没任何输出
        watch(arr1, (newArr1, oldArr1) => {
            console.log(newArr1, oldArr1)
        })

        // 采用函数形式,新旧值不一样
        watch(() => [...arr1.value], (newArr1, oldArr1) => {
            console.log(newArr1, oldArr1) // (3) [8, 2, 3] (3) [1, 2, 3]
        })

        let changeArr1 = () => {
            arr1.value[0] = 8
        }

        return { arr1, changeArr1 }
    },
    template
}

总结:直接传一个ref响应式对象无效,需要传一个getter函数,新旧值不一样

侦听一个 reactive的响应式对象

无多层嵌套的对象

import { reactive, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{person.name}}{{person.age}}</div>
    <button @click="person.age+=1">年龄加一</button>
`

export default {
    setup: function(){
        let person = reactive({
            name: '张三',
            age: 18
        })
        
        // 拿不到旧值
        watch(person, (newValue, oldValue) =>{
            console.log('年龄变化了', newValue, oldValue)
        })

        // 能拿到旧值
        watch(()=>({...person}), (newValue, oldValue) =>{
            console.log('年龄变化了', newValue, oldValue)
        })

        return { person }
    },
    template
}

对于reactive的响应式对象,默认是开启深度侦听的,且通过配置深度侦听为false是无效的

reactive一般是用来侦听一个对象的,所以这里不介绍侦听单个值类型的情况

总结:直接传一个reactive响应式对象无法拿到旧值,需要传一个getter函数才能拿到旧值

多层嵌套的对象

import { reactive, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{person.name}}</div>
    <div>{{person.age}}</div>
    <div>{{person.scores}}</div>
    <button @click="person.scores.english+=1">英语分数加一</button>
`

export default {
    setup: function () {
        let person = reactive({
            name: '张三',
            age: 18,
            scores: {
                math: 100,
                chinese: 90,
                english: 80
            }
        })

         // 新旧值一样
        watch(person, (newValue, oldValue) => {
            console.log('第一个侦听器:person变化了', newValue, oldValue)
        }, { deep: true })

        // 新旧值一样
        watch(() => ({ ...person }), (newValue, oldValue) => {
            console.log('第二个侦听器:person变化了', newValue, oldValue)
        }, { deep: true })

        return { person }
    },
    template
}

总结:直接传一个reactive响应式对象还是传一个getter函数都无法拿到旧值

侦听一个 reactive的响应式数组

import {  reactive, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{arr2}}</div>
    <button v-on:click='arr2[0]=9'>改为9</button> <!-- 这里不用.value -->
`

export default {
    setup: function () {
        let arr2 = reactive([4, 5, 6])

        // 是否配置deep为true都是无效的,新旧值一样
        watch(arr2, (newArr2, oldArr2) =>{
            console.log(newArr2, oldArr2) // Proxy(Array) {0: 9, 1: 5, 2: 6} Proxy(Array) {0: 9, 1: 5, 2: 6}
        })

        // 采用函数形式,新旧值不一样
        watch(()=>[...arr2], (newArr2, oldArr2) =>{
            console.log(newArr2, oldArr2) // (3) [9, 5, 6] (3) [4, 5, 6]
        })

        return { arr2 }
    },
    template
}

总结:直接传入一个reactvie响应式数组,新旧值一样;传入一个getter函数,新旧值不一样

配置对象

在Vue 3的 watch 函数中,配置对象(watch API 的第三个参数)包含以下选项:

  1. immediate(boolean):

    • 默认值:false
    • 如果设置为 true回调函数 将在初始渲染时立即执行一次,即使侦听的数据在初始时没有发生变化。
    watch(
     () => someData,
     (newValue, oldValue) => {
       // 
     },
     {
       immediate: true,
     }
    );
    
  2. deep(boolean):

    • 默认值:false,对于reactive默认是true
    • 如果设置为 true,则会递归地侦听对象内部的属性变化。注意,在Vue 3中,deep 不再支持深度侦听数组。
    watch(
     () => someData,
     (newValue, oldValue) => {
       // 回调函数
     },
     {
       deep: true,
     }
    );
    
  3. flush(string):

    • 默认值:'pre'
    • 指定在何时触发回调函数,有三个选项:
      • 'pre':在依赖变化之前触发。
      • 'post':在依赖变化之后触发。
      • 'sync':与依赖变化同步触发。

    默认情况下,用户创建的侦听器回调,都会在依赖数据变化之后, Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。通过设置flush为 ‘post’,拿到的就是更新后的DOM

    import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'
    
    let template = `
       <div id="count">{{count}}</div>
       <button @click="changeCount">点击</button>
       <button @click="stopWatch">停止侦听</button>
    `
    
    export default {
       setup: function () {
           let count = ref(0)
    
           let stopWatch = watch(count, () => {
               let div = document.querySelector('#count')
               console.log(div.innerHTML)
           }, {
               flush: 'post'
           })
    
           let changeCount = () => {
               count.value++
           }
    
           return { count, changeCount, stopWatch }
       },
       template
    }
    

停止侦听

在Vue 3的Composition API中,你可以通过 watch 函数返回的停止函数来停止侦听。

import { ref, watch } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js'

let template = `
    <div>{{count}}</div>
    <button @click="count++">点击</button>
    <button @click="stopWatch">停止侦听</button>
`

export default {
    setup: function () {
        let count = ref(0)

        let stopWatch = watch(count, (newVal, oldVal) => {
            console.log(newVal, oldVal)
        })

        return { count, stopWatch }
    },
    template
}

在这个例子中,watch 函数返回一个停止函数 stopWatch,然后我们可以在组件的其他地方调用它来停止侦听。在这个例子中,我们通过调用 stopWatch 方法来停止侦听。实际上,你可以在任何地方调用 stopWatch 函数,以停止对数据的侦听。

总结

watch的第一个参数的传递方式:

对于ref的响应式数据对象

  1. 单个数据值: 直接传一个ref包装后的响应式对象即可,新旧值不一样。例如:watch(counter, (newValue, oldValue) => {...})
  2. 多个数据值: 使用watch侦听多个数据源的变化,直接将多个ref响应式对象组成的数组传递给watch,新旧值不一样。例如:watch([name, age], ([newName, newAge], [preName, preAge]) => {...})
  3. 一个ref的对象(无多层嵌套): 无需开启深度侦听,直接传一个ref响应式对象无效,需要传一个getter函数,新旧值不一样。例如:watch(() => ({...person.value}), (newVal, oldVal) => {...})
  4. 一个ref的对象(多层嵌套): 如果有多层嵌套,需要开启深度侦听,可以直接传一个ref响应式对象或者使用getter函数,新旧值一样。例如:watch(person, (newVal, oldVal) => {...}, { deep: true })watch(() => ({...person.value}), (newVal, oldVal) => {...}, { deep: true })
  5. 一个ref的数组: 直接传一个ref响应式对象无效,需要传一个getter函数,新旧值不一样。例如:watch(() => [...arr1.value], (newArr1, oldArr1) => {...})

对于reactive 的响应式数据对象

  1. 一个reactive的对象(无多层嵌套):直接传一个reactive响应式对象,新旧值一样;传入一个getter函数,新旧值不一样。例如:watch(() => ({...person}), (newVal, oldVal) => {...})

  2. 一个reactive 的对象(多层嵌套):直接传一个reactive响应式对象还是传一个getter函数都无法拿到旧值。

  3. 一个reactive 的数组:直接传入一个reactvie响应式数组,新旧值一样;传入一个getter函数,新旧值不一样。watch(() => [...arr1], (newArr1, oldArr1) => {...})

一般来说,我们只是用ref包装的单个数据值,reactive包装的单层对象,reactive包装的多层对象,reactive包装的数组,所以我们只需要记相应的watch使用方法就好了

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值