面试官:讲讲vue3的watch,简单数据类型就别BB了,直接开始对象吧

95 阅读2分钟

Watch的官方定义

在组合式 API 中,我们可以使用watch( )函数在每次响应式状态发生变化时触发回调函数。

官方示例

const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

1、第一个参数一定要对象,不一定必须是响应式对象。如果传递简单数据类型,vue会在控制台提醒

image.png 2、watch函数可以传递一些其他配置,诸如deep、immediate、once,以下为每个配置项的含义(摘自官方文档)

  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。在 3.5+ 中,此参数还可以是指示最大遍历深度的数字。

其中,immediate和once在实际使用中基本没有什么副作用,而deep在开发中可能会带来一些困扰。因此,本文主要探讨Deep这一配置项,在响应式对象、数组这两种场景下带来的影响以及一些注意项。

Deep和响应式对象

实验代码

<template>
  <div>
    <div>{{ `对象:${JSON.stringify(obj)}` }}</div>
    <button @click="objChangePara">变更对象属性</button>
    <button @click="changeObj">替换对象</button>
  </div>
</template>
<script setup>
const obj = ref({ size: 12 })
const objChangePara = () => {
  obj.value.size = 15
  console.log('变更对象属性--------')
}
const changeObj = () => {
  obj.value = { number: 20 }
  console.log('整个替换对象--------')
}
console.log('初始obj', obj.value)
watchEffect(async () => {
  console.log('我是WatchEffect,监听对象', obj.value)
})
watch(obj, (newval, oldval) => {
  console.log('------采用watch(obj)------')
  console.log('oldval:', oldval)
  console.log('newval:', newval)
  console.log('------------------')
})
watch(
  obj,
  (newval, oldval) => {
    console.log('------采用watch(obj)deep------')
    console.log('oldval:', oldval)
    console.log('newval:', newval)
    console.log('------------------')
  },
  { deep: true }
)
</script>

我们先后执行变更对象单个属性、完全替换整个对象两个操作,并使用带有deep配置、不带deep配置以及普通的WatchEffect函数进行监听。实验结果为:

  1. 对于变更单个属性操作,仅带有deep配置的监听器监听到变化,但是新旧值保持了一致!
  2. 对于完全替换对象操作,三类监听器均监听到了变化,新旧值均正确输出!

image.png

Deep和响应式数组

vue3中另一个头疼就是数组的变化,数组的变更方法包括:vue封装的诸如push、pop等函数;直接更改数组内单个元素的属性;直接替换整个数组。针对这三类变更操作,我们采用:普通监听方式、deep监听方式以及对数组长度的监听三类监听方式。

<template>
  <div>
    <div>{{ `数组:${JSON.stringify(list)}` }}</div>
    <button @click="pushItem">push</button>
    <button @click="changeVal">改名</button>
    <button @click="changeList">替换数组</button>
  </div>
</template>
<script setup>
const list = ref([{ name: '张三' }])
const pushItem = () => {
  list.value.push({ name: '王五' })
  console.log('数组push了--------')
}
const changeVal = () => {
  list.value[0].name = '张伟'
  console.log('数组变更内部元素属性了--------')
}
const changeList = () => {
  list.value = [{ name: '阿猫' }, { name: '阿狗' }, { name: '阿牛' }]
  console.log('数组被整个替换--------')
}
console.log('初始list', JSON.stringify(list.value))

watch(
  list,
  (newval, oldval) => {
    console.log('------采用watch(list)deep------')
    console.log('oldval:', JSON.stringify(oldval))
    console.log('newval:', JSON.stringify(newval))
    console.log('------------------')
  },
  { deep: true }
)
watch(list, (newval, oldval) => {
  console.log('------采用watch(list)------')
  console.log('oldval:', JSON.stringify(oldval))
  console.log('newval:', JSON.stringify(newval))
  console.log('------------------')
})
watch(
  () => list.value.length,
  (newval, oldval) => {
    console.log('------采用watch(list.value.length)------')
    console.log('oldval:', oldval)
    console.log('newval:', newval)
    console.log('------------------')
  }
)
</script>

1. 对于使用vue官方封装的操作方法——push方法。deep监听和数组长度的监听方式均达到监听效果,而普通的监听方式无法监听到,但是deep监听的新旧值是一致的!

image.png

2. 对于直接修改单个元素的属性操作。只有deep监听方法达到监听效果!

image.png

3. 对于整个替换数组的操作。三种监听方式均达到了监听效果,同时新旧值是不一致的。

image.png

结论

综合考虑,使用watch函数监听普通对象时,如果不涉及对下层属性的监听,可以关闭deep配置。如果只想监听到浅层属性的变更,可以为deep属性指示最大遍历深度的数字,降低计算消耗。

而对于数组的监听,建议顺手加上deep配置,否则连最基本的push操作都无法监听到!!!

当然如果实际场景中,每次操作都会引起数组长度发生变化,那么监听数组长度也是可以的。