侦听器

@Watchers
Overview
watch 主要用于监听响应式数据的变化,并在变化时执行副作用操作 - allow you to observe changes in your reactive state and perform side effects accordingly.
监听响应式数据源;当数据源发送变化时,在回调函数中处理响应的逻辑
使用 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 函数:一个有返回的函数,特别适合侦听对象中的某个字段数据
. 多个响应式数据源组成的数组
实际开发中,更多是使用函数 Getter 来侦听数据的变化
Callback
侦听时,回调函数会使用2个参数来区分新值 newValue 和旧值 oldValue
通常我们只关心新值 newValue,即:一个参数就够了
若侦听的是 ref 对象的属性,则新值旧值一样,是同一个对象
若侦听的是 ref 对象,则新值是新对象,旧值是老对象,不是同一个对象
可以是异步函数 async-await
onCleanup:回调函数的可选参数,是一个函数,用来注册清理回调;清理回调会在该副作用下一次执行前被调用
Options
1 立即侦听 immediate
watch 默认是懒执行的:仅当数据源变化时,才会执行回调
有时,需要在源变化之前就开始执行回调:设置配置项 immediate为true
如购物车总价,每次进入页面时都先结算一次;此时新值是当前值,而旧值未定义;同初恋一样,无前任
2 深度侦听 deep
默认情况下,只侦听简单类型响应式对象,不能侦听对象的属性或子对象
侦听复合对象内部属性时,需要开启深度侦听:设置 deep 为true
深度侦听需要遍历被侦听对象中的所有嵌套的属性;当用于大型数据结构时,开销很大,请只在必要时才使用它
侦听 reactive 对象时,默认开启 deep,且无法关闭
停止侦听
在大多数情况下,你无需关心怎么停止一个侦听器
要手动停止一个侦听器,请调用 watch 返回的函数
const stopWatch = watch(source, callback)

// 当已不再需要该侦听器时:
stopWatch()
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);
})
复合数据
直接侦听复合数据,某一属性变化并 不会触发 监听 - watch 只会监听 user 引用的变化,而不会监听 user 对象内部属性的变化
改进1:开启深度侦听:设置 deep为 true
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 })
改进2:利用 函数 获取侦听属性 - getter 函数,仅仅当该属性变化时才触发监听
id 变化时,触发侦听;其它属性变化时,不触发
不要遗漏 value
watch(() => user.value.id, (newv, oldv) => {
  console.log('user id is watched', newv, oldv);
})
reactive
侦听 reactive 对象时,默认开启 deep,且无法关闭
侦听的是对象某个属性的变化;当某个属性变化时,会被 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.id, (newv, oldv) => {
  console.log('user id is watched', newv, oldv);
})
利用 函数 获取侦听的属性 - 复合属性
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);
})
Summary & Homework
Summary
watch 常用于触发异步操作,比如根据用户输入去发起 API 请求获取数据
可以侦听一个或多个响应式数据
可以使用 getter 函数侦听响应式数据的特定属性
Homework
[] 分析下面代码的功能
watch(msg, (newv, oldv) => {
    str.value = newv.split('').reverse().join('')
})
[] 购物车
总价

侦听购物车商品长度或某个商品数量

选中某个商品,发生变化时,统计总价

修改选中商品的数量,更新总价

全选初判断

计算总价还要判断是否是全选;当选择了所有商品后,应为全选状态

继续选中商品,当选中商品的种类数量等于待选商品种类的数量时,应为全选

全选再判断

侦听购物车商品的全选对应的多选按钮 checkbox;选中 true,为全选;否则取消全选

[] 侦听仓库:仓库状态发生变化时,给出相应的提示
更多信息,请访问 仓库 - Pinia
[] 侦听模态框的变化
用户信息采集过程中,非法、无效等数据校验,前端可以使用弹出窗口给出提示;一定时间后弹出窗口消失
需要在每个逻辑的校验中,弹出窗口、消失窗口
可以侦听弹窗的变化,当出现|条件为真时,开始倒计时,然后停止倒计时,关闭弹窗
watch(isModalOpen, (newval, oldval) => {
  if (newval) {
    setTimeout(() => {
    isModalOpen.value = false
    }, 3000);
  }
})