前端面试总爱问的watch,你还没搞懂吗?五种情况带你彻底搞懂!

275 阅读7分钟

首先我们还是老规矩,先介绍他是什么

🐟watch(监听器)

watch用于监听特定数据的变化,并在数据变化的同时执行回调函数。可以监听一个到多个的数据变化,执行自定义的逻辑.

image.png 上面是Vue官方文档定义的,Vue3中的watch只能监听以上四种数据

🐟情况一 监视【ref】定义的【基本类型】数据

我们来写一段代码,实现简单的数字加一,

<template>
  <div>
    当前数字为<h2>{{ sum }}</h2>
    <button @click="changesum">add</button>
  </div>
</template>

<script setup>
import { ref, watch } from "vue";
let sum = ref(0);
function changesum() {
  sum.value += 1;
}
watch(sum, (newValue, oldValue) => {
  console.log('变化了', newValue, oldValue);
});
</script>

<style lang="scss" scoped></style>

watch函数用于监听响应式数据的变化。在你提供的代码中,watch(sum, (newValue, oldValue) => {...})的作用是监听sum这个响应式数据的变化,其中newvalue是变化后的,oldvalue是变化前的,点击button,看控制台的输出

image.png

很多人觉得既然我的sum是ref这个方法所创建的,那为什么监视的时候,不用sum.value呢,我们来试试看

image.png 控制台输出了这个警告,我给大家翻译一下

image.png

控制台输出的是watch只能监视四种数据,即开头写的四种,但是我们监视的是sum里面的value,所以就不能成功.所以咱们还是用sum,要记住一下啦! 既然我们可以开始监视,呢么我们也可以停止监视,在 Vue.js 中,可以使用watch的返回值来停止对响应式数据的监视。为啥是用watch的返回值来停止呢,他是为了提供清晰、灵活和可维护的编程体验而设计的一种方式,符合现代前端开发对于高效、可管理代码的需求。

我们来设置如果sum>=10了我们就停止监视,下面是代码的实现

const stopwatch = watch(sum, (newValue, oldValue) => {
  console.log('变化了', newValue, oldValue);
  if (newValue >= 10) {
    stopwatch()
  }
});

image.png 你会发现到了10的时候,就停止了监视,没有输出 '变化了 X X'

🐟情况二 监视【ref】定义的【对象类型】数据

我们都知道ref既可以创造定义基本类型也可以创造对象类型的响应式数据,我们的情况二就是针对于对象类型,我们来看一段代码

<template>
 <div>
   <h2>姓名:{{ person.name }}</h2>
   <h2>年龄:{{ person.age }}</h2>
   <button @click="changeName">改名字</button>
   <button @click="changeAge">改年龄</button>
   <button @click="changePerson">改整个人</button>
 </div>
</template>

<script setup>
import { ref, watch } from "vue";
let person = ref({
 name: '张三',
 age: 18
})
const changeAge = () => {
 person.value.age += 1
}
const changeName = () => {
 person.value.name += "~"
}
const changePerson = () => {
 person.value = { name: '李四', age: 30 }
}
watch(person, (newValue, oldValue) => {
 console.log('对象变化了', newValue, oldValue);
})
</script>

<style lang="scss" scoped></style>

我们定义了一个含有age,name属性的对象Person,然后做了三个改变它的按钮,分别是改变姓名,年龄和整个人,还对Person进行了监视,我们来看一下,这三个按钮哪几个会触发监控,

image.png 分别点击了前两个按钮,控制台都没有任何输出,点击最后一个按钮

image.png 控制台输出的内容表示正在监视的对象person发生了变化。

  • Proxy(Object) {name: '李四', age: 30}是新值newValue,表示对象变化后的状态,即当前person的值为一个包含name属性为“李四”、age属性为 30 的对象。
  • Proxy(Object) {name: '张三~', age: 19}是旧值oldValue,表示对象变化前的状态,即之前person的值为一个包含name属性为“张三~”、age属性为 19 的对象。

我们发现它监视的是对象的地址值,那如果我们想监视他的内部属性呢?

我们可以这样做在里面再加一个参数{deep:true}

watch(person, (newValue, oldValue) => {
 console.log('对象变化了', newValue, oldValue);
},{deep:true})

再去试一试看,

image.png 发现现在就算改变内部属性,也可以引发监控,但是我们发现他们里面的值,每次newValueoldValue都是一模一样的,这是为什么呢?

当你在changeAgechangeName方法中直接修改对象的属性时,Vue 的响应式系统会检测到这个变化,并触发相关的依赖更新。但是,由于只是修改了对象的属性而没有改变对象的引用,所以在watch的回调中,新旧值指向的是同一个对象。

例如,在changeName方法中给person.value.name添加一个 “~”。Vue 检测到这个变化后,会更新依赖于person.name的地方。在watch的回调中,newValueoldValue实际上都是指向同一个person对象的引用,只是这个对象的name属性值发生了变化。

image.png

image.pngchangePerson方法中,直接将person.value重新赋值为一个全新的对象,这就导致了新旧值是完全不同的两个对象。

这个时候还可以再加一个参数

  watch(person, (newValue, oldValue) => {
  console.log('对象变化了', newValue, oldValue);
},{deep:true,immediate:true})

image.png 这个 immediate: true 的调用,watch 会在创建时立即触发一次回调函数,传入当前被监视的值作为 newValue,旧值 oldValue 为 undefined。这在一些场景下很有用,比如你希望在组件创建后立即执行一些基于初始值的操作。 除了这两个,watch还有其他的配置选项,大家可以去自行了解,我就不多做赘述了

🐟情况三 监视【reactive】定义的【对象类型】数据

我们用reactive来创建一个响应式数据

<template>
 <div>
   <h2>姓名:{{ person.name }}</h2>
   <h2>年龄:{{ person.age }}</h2>
   <button @click="changeName">改名字</button>
   <button @click="changeAge">改年龄</button>
   <button @click="changePerson">改整个人</button>
 </div>
</template>

<script setup>
import { ref, watch } from "vue";
let person = ref({
 name: '张三',
 age: 18
})
const changeAge = () => {
 person.value.age += 1
}
const changeName = () => {
 person.value.name += "~"
}
const changePerson = () => {
 person.value = { name: '李四', age: 30 }
}
watch(person, (newValue, oldValue) => {
 console.log('对象变化了', newValue, oldValue);
})
</script>

<style lang="scss" scoped></style>

这个时候我们点哪几个按钮会实现他们的更新呢,我们来试一试

image.png 发现三个都可以被watch监视到,我们发现reactive定义的响应式数据是默认开启深度监视的

<template>
<div>
 <h2>测试 :{{ obj.a.b.c }}</h2>
 <button @click="test">修改obj.a.b.c</button>
</div>
</template>

<script setup>
let obj = reactive({ a: { b: { c: 666 } } })
const changeAge = () => {
person.age += 1
const test = () => {
obj.a.b.c = 888
}
watch(obj, (newValue, oldValue) => {
console.log('变化了', newValue, oldValue);
})
</script>

<style lang="scss" scoped></style>

点击修改obj.a.b.c按钮

image.png 说明他就是隐式开启深度监视,且无法被隐蔽!

🐟情况四 监视【reactive,ref】定义的【对象类型】中的某个属性

老样子先来一段代码看看

<template>
  <div>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>汽车:{{ person.car }}</h2>
    <button @click="changeName">改名字</button>
    <button @click="changeAge">改年龄</button>
    <button @click="changeC1">改c1</button>
    <button @click="changeC2">改c2</button>
    <button @click="changeCar">改car</button>
  </div>
</template>

<script setup>
import { reactive, watch } from "vue";
let person = reactive({
  name: '张三',
  age: 18,
  car: {
    c1: '奔驰',
    c2: '宝马'
  }
})

const changeName = () => {
  person.name += "~"
}
const changeAge = () => {
  person.age += 1
}
const changeC1 = () => {
  person.car.c1 = '奥迪'
}
const changeC2 = () => {
  person.car.c2 = '大众'
}
const changeCar = () => {
  person.car = {
    c1: '雅迪',
    c2: '艾玛'
  }
}
watch(person, (newValue, oldValue) => {
  console.log('变化了', newValue, oldValue);

})
</script>

<style lang="scss" scoped></style>

如果这个时候我们只想监视他的name,其他的不想监视,可不可以用person.value,

image.png 不行,他不是上面的四种数据之一,呢我们只能想办法让他变通一下了,可以让他用getter函数(就是用来返回一个值)来实现, 我们直接用一个匿名函数

watch(()=>{person.name}, (newValue, oldValue) => {
  console.log('person.name变化了', newValue, oldValue);

})

image.png 只有改名字才会触发监视,我们得到一下结论,该属性为基本类型时,要写成函数式

image.png

但如果要监视对象类型呢?

watch(person.car, (newValue, oldValue) => {
  console.log('person.car变化了', newValue, oldValue);

})

我们发现person.car可以直接写,也不会报错,这是因为person.car是一个对象,但是奇怪的又来了,只有按钮改c1和改c2被监视了,改car的按钮并没有被监视

image.png 我们试试用匿名函数

watch(()=>person.car, (newValue, oldValue) => {
  console.log('person.car变化了', newValue, oldValue);

})

这个时候变成了,按钮改c1和改c2没有被监视了,改car的按钮被监视了,这个时候我们再加一个参数

watch(()=>person.car, (newValue, oldValue) => {
  console.log('person.car变化了', newValue, oldValue),{deep:true}
})

这个时候,三个按钮就全都生效了!

image.png

监视的要是对象里的属性,呢么最好写函数式,注意点:若是对象监视的是地址值,则需要关注对象内部,需要手动开启深度监视!

🐟情况五 监视上述多个数据

  • 可以在 watch 的第一个参数中传入一个数组,数组中包含多个要监视的表达式。

image.png

watch([() => person.car, () => person.name], (newValue, oldValue) => {
  console.log('person.car变化了', newValue, oldValue);

}, { deep: true })

除了person.age都可以监视!

🐟END

总之,watch 在 Vue 中是一个非常强大的工具,可以根据不同的需求来监视各种类型的响应式数据。在使用时,需要根据具体情况选择合适的监视方式,并注意性能和逻辑的复杂性。