「Vue3学习篇」-watchEffect()、watchPostEffect()、watchSyncEffect()

『引言』

在日常开发中,慢慢地就会发现遇到的需求总是多变的,如果要实现下面这个需求会怎么做呢?

🙋🙋‍♂️提问 🚩:要实现第一次渲染页面的时候,watch里的回调函数就执行一遍,怎么做❓🤔🤔

回答 📒:使用watch实现,就是把回调函数直接提取出来,进入页面执行一遍,这样操作比较繁琐。其它方法的话,可以使用今日的主角watchEffect。

watchEffect相当于将 watch 的依赖源和回调函数合并,它会立即执行传入的一个函数,自动收集其依赖的函数,监视响应式数据的变化,每次变化都会触发回调函数的重新执行。

watchEffect 监听是立即执行的,会默认初始时就会执行第一次, 从而可以收集需要监听的数据。

『watchEffect()』

『定义』

【官方解释】立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

  • 详细信息

    第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。

    第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

    默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

    返回值是一个用来停止该副作用的函数。

【我的理解】 watchEffect 可以看成是 watch 和 side effct,那么就可以理解成,在观察『watch』到变化后执行一些操作『side effct』。

『用法』

watchEffect(effect,options)

『watchEffect参数说明』

  • 第一个参数:(必传)effect 函数,收集依赖,在组件初始化时立即执行一次。

    并且 effect 函数可以接受一个 onInvalidate 函数参数,该参数执行并传入一个 callback ,每次监听回调执行前都会执行该 callback。

  • 第二个参数对象(非必传): flush 、 onTrack 、 onTrigger。『onTrack 、 onTrigger』只能在开发模式下工作。

    • flush: 跟 watch() 的flush 完全一致,『 'pre' | 'post' | 'sync' 』默认:'pre'。
    • onTrack:跟踪之前会触发该函数,收集了多少个依赖就触发多少次,返回对应依赖信息。
    • onTrigger:依赖更改就触发执行,同步的,没有缓冲回调。

『官网示例🌰』

『注意』

  • watchEffect不会返回一个停止副作用的函数。如果需要停止副作用函数的执行,需要使用watch API,并通过返回的停止函数来实现。
  • 使用时不需要具体指定监听的谁,回调函数内直接使用就可以。
  • 只能访问当前最新的值,访问不到修改之前的值。

『示例🌰:watchEffect 监听数据』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{info.address.city}}</p>
    <p>描述:{{info.description}}</p>
    <button @click="modifyInfo">
      修改数据
    </button>
  </div> 
</template>

<script setup>
import { ref, reactive, watchEffect } from 'vue'
    
   const name = ref('pupu') 
   const age = ref(10)
   const info = reactive({
      hobbies: ['唱歌','画画'],
      address: {
          provice: '浙江省',
          city: '杭州市'
      },
      description: '一点也不可爱,不喜欢吃蜂蜜!',
    })
    
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        info.hobbies = ['打羽毛球','旅游']
        info.address.provice = '云南省'
        info.address.city = '丽江市'
        info.description = '非常的可爱,特别喜欢吃蜂蜜!'   
    } 
    // 情况1: 监听ref定义的数据
    watchEffect(() => {
      console.log(name.value, 'name')
    })
    // 情况2: 监听reactive定义的数据
    watchEffect(() => {
      console.log(info.address.provice, info.address.city, 'address')
    })
    // 情况3: 监听reactive定义的对象数据
    watchEffect(() => {
      console.log(info, 'info对象')
    })
    // 情况4: watchEffect什么时候执行
    watchEffect(() => {
      console.log("执行了")
    })
</script>

『效果展示』

『代码解析』

上述示例代码中,分别介绍了几种情况。

  • 情况1: 监听ref定义的数据,这种情况下监听的是『name属性』,在回调函数中打印了一下『name』的值,可以在控制台中看到,第二次点击修改按钮,『watchEffect』触发了。

  • 情况2: 监听reactive定义的数据,这时监听的是『info.address.provice和info.address.city』,同样的在回调函数中打印一下,可以在控制台中看到,第二次点击修改按钮,『watchEffect』也触发了。

  • 情况3: 监听reactive定义的对象数据,这时监听的是『info对象』,也在回调函数中打印了一下,可以看到第一次立即执行时执行了,第二次点击修改按钮后,改变『info』的『watchEffect』没触发。

    这是因为watchEffect只会跟踪回调函数中使用的属性,不会递归跟踪对象中所有的属性。

  • 情况4: watchEffect什么时候执行,这种情况可以看到第一次立即执行时执行了,第二次并没有执行,控制台没有打印任何信息。

    这是因为『watchEffect』使用时不需要具体指定监听的谁,回调函数内直接使用就可以,那也就是回调函数中使用了就执行,没使用就不会执行。

『示例🌰:watchEffect的副作用』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{info.address.city}}</p>
    <p>描述:{{info.description}}</p>
    <button @click="modifyInfo">
      修改数据
    </button>
  </div> 
</template>

<script setup>
import { ref, reactive, watchEffect } from 'vue'
    
   const name = ref('pupu') 
   const age = ref(10)
   const info = reactive({
      hobbies: ['唱歌','画画'],
      address: {
          provice: '浙江省',
          city: '杭州市'
      },
      description: '一点也不可爱,不喜欢吃蜂蜜!',
    })
    
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        info.hobbies = ['打羽毛球','旅游']
        info.address.provice = '云南省'
        info.address.city = '丽江市'
        info.description = '非常的可爱,特别喜欢吃蜂蜜!'   
    } 
    
    watchEffect(
      (onInvalidate) => {
        console.log(name.value, 'name')
        onInvalidate(()=> {
          console.log('执行了onInvalidate')
        })
      })
</script>

『效果展示』

『代码解析』

因为watchEffect就是侦听到依赖的变化后执行某些操作,所以watchEffect的回调函数就是一个副作用函数。

watchEffect侦听副作用传入的函数可以接收一个 onInvalidate 函数作为入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

  • 副作用即将重新执行时(即依赖的值改变)
  • 侦听器被停止 (通过显示调用返回值停止侦听,或组件被卸载时隐式调用了停止侦听)

在上述代码中,定义了一些数据、点击按钮修改信息、对『name』数据进行监听。在对『name』数据进行监听时,初始化时会在控制台打印『name』的值为『pupu』,由于点击按钮会修改『name』的值,对『name』的值进行了更新,所以副作用就会重新执行,此时onInvalidate的回调函数会被触发,控制台就打印了执行了onInvalidate,之后会执行副作用函数,打印『name』的值为『wnxx』。

『示例🌰:关闭 watchEffect 监听』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{info.address.city}}</p>
    <p>描述:{{info.description}}</p>
  </div> 
</template>

<script setup>
import { ref, reactive, watchEffect } from 'vue'
    
   const name = ref('pupu') 
   const age = ref(10)
   const info = reactive({
      hobbies: ['唱歌','画画'],
      address: {
          provice: '浙江省',
          city: '杭州市'
      },
      description: '一点也不可爱,不喜欢吃蜂蜜!',
    })
    
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        info.hobbies = ['打羽毛球','旅游']
        info.address.provice = '云南省'
        info.address.city = '丽江市'
        info.description = '非常的可爱,特别喜欢吃蜂蜜!'   
    } 
    
   const unwatchEffect = watchEffect((onInvalidate) => {
        console.log(name.value, 'name')
        onInvalidate(()=> {
          console.log('执行了onInvalidate')
        })
    })

    setTimeout(()=> {
      modifyInfo()
      unwatchEffect()
    }, 1000)
</script>

『效果展示』

『代码解析』

关闭watchEffect 监听,其实非常简单,上述代码中创建一个 watchEffect 监听器,只需要将调用一下就关了。

还有就是在上述代码中,关闭watchEffect 监听,执行unwatchEffect函数停止监听,就会触发onInvalidate的回调函数。

『watchPostEffect()』

『定义』

【官方解释】watchEffect() 使用 flush: 'post' 选项时的别名。

『注意』

现在有这样一个疑问,也是通过一个示例进行解答,到底是什么疑问,一起看看下面👇。

提问🚩:在监听器的回调函数中获取DOM,这个时候的DOM是更新前的还是更新后的❓🤔🤔

『示例🌰』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{info.address.city}}</p>
    <p>描述:{{info.description}}</p>
    <button @click="modifyInfo">
      修改数据
    </button>
  </div> 
</template>

<script setup>
import { ref, reactive, watchEffect, watchPostEffect } from 'vue'
    
   const name = ref() 
   const age = ref(10)
   const info = reactive({
      hobbies: ['唱歌','画画'],
      address: {
          provice: '浙江省',
          city: '杭州市'
      },
      description: '一点也不可爱,不喜欢吃蜂蜜!',
    })
    
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        info.hobbies = ['打羽毛球','旅游']
        info.address.provice = '云南省'
        info.address.city = '丽江市'
        info.description = '非常的可爱,特别喜欢吃蜂蜜!'   
    } 
    // 情况1
    watchEffect(
      () => {
        console.log(name.value, 'name')
      })
    // 解决办法1
    watchEffect(
      ()=>{ 
        console.log(name.value, '更新后的DOM节点name1')
      },
      {
       flush: 'post'
      })
    // 解决办法2
    watchPostEffect(() => {
      console.log(name.value, '更新后的DOM节点name2')
    })
</script>
 

『效果展示』

『代码解析』

上述代码中,监听的是『name』的值,当监听『name』的值改变的时候,『vue组件会更新、监听器会回调』。

一般监听器的回调会比vue组件的更新先一步调用。所以在监听器回调中,访问的DOM节点是未更新的。

当想要在监听器回调中访问更新后的DOM节点时,可以传递一个flush: 'post'参数,或者使用更简便的api watchPostEffect()。

『watchSyncEffect()』

『定义』

【官方解释】 watchEffect() 使用 flush: 'sync' 选项时的别名。

在组件更新之前或更新之前,立即同步调用它。通常,它在onBeforeUpdate之前调用。

『watch 与 watchEffect 的区别』

不同点:

  • 『watch』
    • 页面第一次加载时不触发watch函数。
    • 可以以数组的形式监听多个参数,如果多个数据同时发生改变,watch只触发一次。
    • 可以获取监听的数据的新值和旧值。
  • 『watchEffect』
    • 页面第一次加载时就会触发 watchEffect 函数。
    • 可以监听多个参数,不能监听对象。
    • 只可以获取监听的数据的新值。
相关推荐
卸任31 分钟前
Vibe coding工具使用心得
前端·ai编程
brien91 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance2 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M2 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写2 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李2 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易2 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马2 小时前
前沿视界
前端
kyriewen2 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易2 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员