侦听器

@Watchers
Overview
watch 主要用于监听响应式数据的变化,并在变化时执行副作用操作
监听响应式数据源;当数据源发送变化时,在回调函数中处理响应的逻辑
使用 watch 函数在每次响应式状态发生变化时触发回调函数
侦听的是响应式数据,不能直接侦听响应式数据的属性
三个:数据源 source、回调函数 callback、配置项 options
需引入包 watch;更多信息,请访问 侦听 - Watcherswatch()
import { watch } from 'vue'
function watch<T>(
    source: WatchSource<T>,
    callback: WatchCallback<T>,
    options?: WatchOptions
): StopHandle
Source
侦听数据类型 - 4种
. 一个 ref 对象 (包括计算属性)
. 一个 reactive 对象
. 一个 getter 函数:一个有返回的函数
. 多个响应式数据源组成的数组
Callback
侦听时,回调函数会使用2个参数来区分新值 newValue 和旧值 oldValue
通常我们只关心新值 newValue,即:一个参数就够了
若侦听的是 ref 对象的属性,则新值旧值一样,是同一个对象
若侦听的是 ref 对象,则新值是新对象,旧值是老对象,不是同一个对象
onCleanup:回调函数的可选参数,是一个函数,用来注册清理回调;清理回调会在该副作用下一次执行前被调用
Options
1 立即侦听 immediate
watch 默认是懒执行的:仅当数据源变化时,才会执行回调
有时,需要在源变化之前就开始执行回调:设置配置项 immediate为true
如购物车总价,每次进入页面时都先结算一次;此时新值是当前值,而旧值未定义;同初恋一样,无前任
2 深度侦听 deep
默认情况下,只侦听简单类型响应式对象,不能侦听对象的属性或子对象
侦听复合对象内部属性时,需要开启深度侦听:设置 deep 为true
侦听 reactive 对象时,默认开启 deep,且无法关闭
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能
ref
一个简单数据
当单击执行增加函数 inc 时,Vue 可以检测到 num 的变化,从而做出反应;如商品数量增加或减少时,总价发现变化
let num = ref(0)
const inc = () =>{
    num.value++
}
watch(num, (newv, oldv) => {
    console.log('num old vs new', oldv, newv);
})
如果侦听 num.value,则会提示
Invalid watch source:  0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.at ... 
多个简单数据
任何一个对象发生变化都会被监听到;是或的关系,不是与的关系
以数组的形式 分别 表示多个数据源:1个数据源变成多个数据源
以数组的形式 分别 显示新值和旧值:1个新值变成多个新值;一个旧值变成多个旧值
[] 信用卡年费免除 - 消费达到一定金额(10000)或消费多少次(6),免除当年年费
let times = ref(0)
let sum = ref(0)
function incTimes() {
    times.value++
}
function incSum() {
    sum.value += 1000
}
watch([times, sum], ([new1, new2], [old1, old2]) => {
    console.log(old1, new1, old2, new2);
})
复合数据
侦听复合数据,需开启深度侦听:设置 deep为 true
任一属性变化都会被 Vue 捕获;但是变化前后都是同一个对象;所以 newv 和 oldv 是一样的,其内部的属性也是一样的
结论:仅仅知道数据属性发生变化,无法获知前后变化情况
let user = ref({
    id: 100,
    name: 'glpla',
    age: 18,
    addr: {
        city: 'cq',
        code: '541000'
    }
})
watch(user, (newv, oldv) => {
    console.log('user is watched', newv.age, oldv.age);
}, { deep: true })
更多的是侦听复合数据某个指定属性
利用 函数 获取侦听属性 - getter 函数
仅仅当该属性变化时才触发回调
age 变化时,触发侦听;code 变化时,不触发
watch(() => user.value.age, (n, o) => {
    console.log(o, n);
})
官文没有使用 value!!!
无需开启 deep
reactive
属性变化
侦听的是对象某个属性的变化;当某个属性变化时,会被 Vue 捕获
新值旧值是 reactive 对象,变化前后都是同一个对象;所以 newv 和 oldv 是一样的,其内部的属性也是一样的
体会对象的引用特性
结论:可以捕获到数据属性变化,但是无法查看属性前后的变化
let user = reactive({
    id: 100,
    name: 'glpla',
    age: 18,
    addr: {
        city: 'cq',
        code: '541000'
    }
})    
watch(user, (newv, oldv) => {
    console.log('user is watched', newv, oldv);
    console.log('user is watched', newv.age, oldv.age);
})
const incAge = () => {
    user.age++
}
捕获属性前后变化
利用 函数 获取侦听的属性 - 简单属性
watch(() => user.age, (newv, oldv) => {
    console.log(oldv, newv);
})
利用 函数 获取侦听的属性 - 复合属性
watch(() => user.addr.code, (n, o) => {
    console.log(o, n);
})
多个属性变化
watch([() => user.age, () => user.addr.code], ([n0, n1], [o0, o1]) => {
    console.log(o0, n0, o1, n1);
})
other
立即侦听
设置配置项 immediate 为 true;其它使用同上
停止侦听器
const stop = watch(source, callback)

// 当已不再需要该侦听器时:
stop()
[] 侦听模态框的变化
用户信息采集过程中,非法、无效等数据校验,前端可以使用弹出窗口给出提示;一定时间后弹出窗口消失
需要在每个逻辑的校验中,弹出窗口、消失窗口
可以侦听弹窗的变化,当出现|条件为真时,开始倒计时,然后停止倒计时,关闭弹窗
watch(isModalOpen, (newval, oldval) => {
    if (newval) {
        setTimeout(() => {
        isModalOpen.value = false
        }, 3000);
    }
})
[] 分析下面代码的功能
watch(msg, (newv, oldv) => {
    str.value = newv.split('').reverse().join('')
})
[] 购物车
总价:侦听购物车商品数量发生变化时,重新统计总价
全选:侦听购物车商品的全选状态;选中,为全选;否则取消全选
[] 侦听仓库:仓库状态发生变化时,给出相应的提示
更多信息,请访问 仓库 - Pinia
实际开发中,更多是使用函数 Getter 来侦听数据的变化
Summary
watch 常用于触发异步操作,比如根据用户输入去发起 API 请求获取数据
可以侦听一个或多个响应式数据
可以使用 getter 函数侦听响应式数据的属性