Vue3+vite+Ts+pinia—第五章 watch与watchEffect

5.1 watch概述

        作用:监视数据的变化(和Vue2中的watch作用一致)

        特点:Vue3中的watch只能监视以下四种数据:

  1. ref定义的数据。
  2. reactive定义的数据。
  3. 函数返回一个值(getter函数)。
  4. 一个包含上述内容的数组。

5.2 监听ref基本类型

        1、Vue2

            对于基本类型的监听,Vue2就是一个函数,里面有newValue和oldValue返回新值和旧值。

watch: {
  sum(newValue, oldValue) {
    ......
  }
}

        2、Vue3

            在Vue3里使用watch要先import引入,而且watch里面传入两个参数:第一个是监视的属性名,第二个是回调函数。

import {watch} from 'vue'
watch(sum, (newValue, oldValue) => {
  ......
})

            这里举一个最简单的例子:点按钮,数字+1。数字属于基本类型,我们对它进行监听:

<template>
  <div class="person">
    <h2>当前求和为:{{sum}}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了', newValue, oldValue)
  })
</script>

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

            注意:watch的第一个参数传的是变量名,而不是它的value值,千万不要写成变量名.value。虽然它是ref类型,真正的值是.value,但watch并非监听它的value值。

5.3 监听ref对象类型

        先回顾一下Vue2监听对象的语法,它是一个对象,里面通过handler方法来获取新值和旧值。

watch: {
  sum: {
    handler(newValue, oldValue) {
      ......
    }
  }
}

        Vue3如果监听的是一个对象类型,这个比较有意思,它会出现两种情况:第一个监听的是整个对象的地址是否改变;第二个是监听对象里的属性是否发生改变。

5.3.1 监听对象地址

        监听整个对象地址是否发生改变,这个是最简单的,直接把对象放进去watch监听就可以了。

        在下面的例子里有3个按钮,”修改名字”和”修改年龄”都是改变对象里的属性值,而”修改整个人”是改变整个对象。

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

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let person = ref({
    name:'张三',
    age:18
  })
  // 方法
  function changeName(){
    person.value.name += '~'
  }
  function changeAge(){
    person.value.age += 1
  }
  function changePerson(){
    person.value = {name:'李四',age:90}
  }
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  })
</script>

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        我们发现,直接把对象放进去watch进行监听,它是无法监听内部属性的变化的,它只能监听整个对象有没有被重新赋值,也就是它的地址有没有改变。

5.3.2 监听对象属性

        在实际的开发中,我们更多的是监听对象里的属性变化。此时需要手动开启深度监听,也就是设置deep: true。

        我们在这里继续做Vue2与Vue3的语法对比:

        1、Vue2

            在Vue2中,需要深度监听,只需要在与handler平级的地方设置deep:true即可。

watch: {
  person: {
    handler(newValue, oldValue) {
      ......
    }
    deep: true
  }
}

        2、Vue3

            在Vue3中,它同样是设置deep:true,区别就在于它是在watch的第三个参数里设置,第三个参数是一个对象,用来设置watch的一些配置项,比如{deep: true}。

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

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        我们发现,设置了deep:true深度监听之后,不管是属性值发生变化,还是整个对象发生变化,它一样能监听得到。

        当然除了deep:true之外,还有immediate:true也是比较常用,这个是立即监听的意思,它是先执行监听的函数,因此在初始化数据的时候,它就会立即调用一次watch监听。它的作用跟Vue2是完全一致的,在这里就不再举例详述。

        注意:我们在监听对象的时候,如果是整个对象发生改变,那么它是可以识别新对象与旧对象的。但如果对象不变,只是内部的属性发生改变,那么它的newValue与oldValue是一致的。

5.3.3 示例代码

        请打开F12控制台观看打印输出信息。

5.4 监听reactive对象类型

5.4.1 reactive与ref的监听对比

        reactive只能定义对象类型,它不像ref那样可以定义基本和对象两种类型。reactive与ref在监听的时候,主要有以下两点区别:

        1、reactive类型是不允许改变整个对象的,也就是说不能给它赋值一个新对象,因此它就不会出现监听整个对象发生变化的情况

        2、默认开启深度监听

5.4.2 监听reactive

        其实监听reactive跟ref是一样的,也是把对象传进去watch里进行监听,不过它比ref更加方便,直接就可以监听到里面的属性变化,不需要设置deep:true。准确来说是Vue3强行设置深度监听,哪怕你设置deep:false也是没用的。

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

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'
  // 数据
  let person = reactive({
    name:'张三',
    age:18
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changePerson(){
    Object.assign(person,{name:'李四',age:80})
  }
  
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  })
</script>

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        注意:示例中修改整个人(整个对象),它实际上是使用了Object.assign()方法,这个方法实际上并没有改变对象地址,它只不过是批量改变对象的各个属性而已。

5.4.3 示例代码

        请打开F12控制台观看打印输出信息。

5.5 监听对象属性

        除了监听整个对象以外,还可以指定监听对象的某一个属性。而对象里的属性,又可以分成两种类型:一个是基本类型的属性,一个是对象类型的属性。

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        上述例子中,前面两个按钮是修改对象里的基本类型,后面3个按钮是修改对象里的对象类型,我们对此逐一分析:

5.5.1 基本类型属性

        1、Vue2

            在Vue2中,监听对象里的基本类型属性,以对象.属性用一个函数返回最新值和旧值。

watch: {
  'person.name'(newValue, oldValue) {
    ......
  }
}

        2、Vue3

            在Vue3监听一个对象,是直接把对象放进去watch里监听即可。现在要监听对象里的属性,我们的第一反应就会把对象.属性放进去监听。

        例如在例子中想监听对象的名字,很容易就会写成watch(person.name, ()=>{}),如下所示:

<template>
  <div class="person">
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeC1">修改第一台车</button>
    <button @click="changeC2">修改第二台车</button>
    <button @click="changeCar">修改整个车</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'

  // 数据
  let person = reactive({
    name:'张三',
    age:18,
    car:{
      c1:'奔驰',
      c2:'宝马'
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  ......
  watch(person.name, (newValue,oldValue) => {
    console.log('person.name变化了',newValue,oldValue)
  })
</script>

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        我们发现,我们直接以对象.属性的方式放进去监听,Vue3是不允许的,它会发出警告:这是一个无效的监听源,它只允许以下四种类型:getter函数、ref类型、reactive类型、以上3种类型组合的数组。

        显然这个person.name只不过是一个字符串,属于基本类型属性,并非以上四种类型之一。

        那么getter函数又是什么呢,其实这个警告说得比较含糊,反而官方文档说得非常接地气:所谓getter函数,它就是返回一个值的函数。

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        文档地址:cn.vuejs.org/api/reactiv…

        既然了解了getter函数,那么我们只需要使用箭头函数将对象属性包裹一下就可以了:

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

        此时它就只监听对象的name属性,其它属性都不会监听:

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

5.5.2 对象类型属性

        1、Vue2

            Vue2监听对象里的对象类型属性,其实跟监听普通对象是一样的,以对象.属性作为键,通过handler方法来获取新值和旧值。

watch: {
  'person.car': {
    handler(newValue, oldValue) {
      ......
    }
  }
}

        2、Vue3

            Vue3对于对象里的对象类型属性的监听,它有两种方式:一是直接监听,二是使用箭头函数包裹后再监听。

            (1)直接监听属性对象

                基本类型需要箭头函数包裹成getter函数才能监听,而对象类型不需要,它直接传入watch即可监听。 

let person = reactive({
  name:'张三',
  age:18,
  car:{
    c1:'奔驰',
    c2:'宝马'
  }
})
function changeC1(){
  person.car.c1 = '奥迪'
}
function changeC2(){
  person.car.c2 = '大众'
}
function changeCar(){
  person.car = {c1:'雅迪',c2:'爱玛'}
}
watch(person.car,(newValue,oldValue)=>{
  console.log('person.car变化了',newValue,oldValue)
})

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

               这里可以看出一个问题,把属性对象直接传入watch监听,它可以监听到对象里每一个属性的变化,但是如果整个对象发生改变,也就是给它赋值一个新的对象,它是无法监听的。

            (2)箭头函数包裹属性对象(推荐)

               除了直接监听,还可以像基本类型那样先包裹一个箭头函数,再进行监听:

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

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

               包裹了箭头函数之后,它反而无法监听里面的属性变化,它只关注对象的地址是否发生改变。如果想既监听内部属性,也监听对象地址,只需要再加上深度监听属性即可,即设置deep:true。

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

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

5.5.3 示例代码

        请打开F12控制台观看打印输出信息。

5.6 监听多个数据

        Vue3它可以同时监听多个数据,不需要分开写多个watch,可以写在一个watch里,将需要监听的属性放进数组里传入watch监听即可。

        这里还是举上述的例子,这次监听的是名字和汽车这两个属性,但不监听年龄属性:

let person = reactive({
  name:'张三',
  age:18,
  car:{
    c1:'奔驰',
    c2:'宝马'
  }
})
function changeC1(){
  person.car.c1 = '奥迪'
}
function changeC2(){
  person.car.c2 = '大众'
}
function changeCar(){
  person.car = {c1:'雅迪',c2:'爱玛'}
}
watch([()=>person.name,person.car],(newValue,oldValue)=>{
  console.log('person.car变化了',newValue,oldValue)
})

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        注意:因为你是同时监听多个属性,因此里面只要某一个属性发生了变化,它都会触发。你传入的是数组,因此它也会返回整个数组,里面就是各个属性的值。

        请打开F12控制台观看打印输出信息。

5.7 解除watch监听

        我们一旦使用了watch,它就会永久监听,我们同样有办法将这个监听解除掉。

        在watch调用的时候,实际上它是有返回值的,我们可以使用变量接收并打印观察一下:

const stopWatch = watch(sum,(newValue,oldValue)=>{
  ......
})
console.log(stopWatch)

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        我们发现它其实是一个函数来的,如果我们想终止监听,只需要执行它即可。

        举一个例子,假设sum从0开始累加1,当大于10的时候取消监听。

<template>
  <div class="person">
    <h2>当前求和为:{{sum}}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  const stopWatch = watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    if(newValue >= 10){
      stopWatch()
    }
  })
</script>

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        请打开F12控制台观看打印输出信息。

5.8 watchEffect

5.8.1 watchEffect与watch的区别

        1、都能监听响应式数据的变化,不同的是监听数据变化的方式不同

        2、watch:要明确指出监听的数据,它是一个惰性监听,你不指定它就不监听

        3、watchEffect:不用明确指出监听的数据(函数中用到哪些属性,那就监听哪些属性)。

5.8.2 watchEffect的使用

        我们同样用一个简单的例子做一个比较,假设有一个需求:当水温达到60度,或水位达到80cm时,给服务器发请求。

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        1、watch实现方式

            watch有多种监听方式,可以对水温和水位分别监听,也可以同时监听。这里我们使用最快捷的方式,使用数组同时监听俩属性:

watch([temp,height],(value)=>{
  // 从value中获取最新的水温(newTemp)、最新的水位(newHeight)
  let [newTemp,newHeight] = value
  // 逻辑
  if(newTemp >= 60 || newHeight >= 80){
    console.log('给服务器发请求')
  }
})

            在这个需求里,涉及到的变量总共是两个,分别是水温和水位,因此watch需要监听这两个属性才能实现。如果需求很复杂,涉及的变量有10个,那么你也只能老老实实的给watch指定这10个变量的监听。

        2、watchEffect实现方式

            watchEffect它不需要你去指定具体监听的属性,你只需要关注业务逻辑,只要代码涉及到的变量,它会帮你自动监听。

            如果用watchEffect来实现上面的需求,将会省去所有监听的属性,变得更加简洁和灵活。

watchEffect(()=>{
  if(temp.value >= 60 || height.value >= 80){
    console.log('给服务器发请求')
  }
})

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

        3、watchEffect的瑕疵

            如果在if条件里使用了或运算符||,它会按照条件顺序进行监听,一旦前面的条件符合,则后续的条件将不会监听。

            继续用上面的例子:当水温达到60度,或水位达到80cm时,给服务器发请求。我们的代码写成if(temp.value >= 60 || height.value >= 80),按照我们的理解,无论是temp还是height变量,只要哪一个条件符合它都会触发。

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

            但watchEffect的机制并非如此,它认为第一个条件是temp是否大于60,如果符合,则height的值无论是多少它都不关心。它只有在第一个条件不符合的情况下,它才会关注第二个条件。

            因此大家在使用watchEffect写逻辑的时候,要小心使用||运算符,如果实在想在watchEffect实现if的或运算符,可以多写一个if来巧妙的绕开或运算符。

watchEffect(()=>{
  if (temp.value >= 60) {
    console.log('给服务器发请求')
  }
  if (height.value >= 80 && temp.value < 60) {
    console.log('给服务器发请求')
  }
})

            上面的代码将if(temp.value >= 60 || height.value >= 80)拆分成两段,如果水温temp大于60就发请求,如果水位超过80且水温不到60也发请求(因为水温到60就不用考虑水位了)。这样也同样能完美实现我们的需求。

Vue3+vite+Ts+pinia—第五章 watch与watchEffect

5.8.3 watchEffect的注意事项

    1、watchEffect是立即监听的,相当于watch配置了immediate为true一样

    2、watchEffect无论是基本类型还是对象类型,它都可以监听到

    3、watchEffect的解除监听方式跟watch是一样的

5.8.4 示例代码

        请打开F12控制台观看打印输出信息。

原文链接:https://juejin.cn/post/7332762964376616960 作者:sowhat88

(0)
上一篇 2024年2月10日 下午4:22
下一篇 2024年2月11日 上午10:06

相关推荐

发表评论

登录后才能评论