Vue3中watchEffect和watch的基础应用详解

 更新时间:2023年07月24日 11:19:11   作者:周莫申  
watch是一个侦听器,默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数,watchEffect是会自动收集函数里面变量的响应式依赖,本文主要来讲讲二者的区别,感兴趣的可以了解一下

watchEffect

watchEffect会自动收集函数里面变量的响应式依赖。在初始化的时候watchEffect会自动执行一次(这是无法阻止的),之后watchEffect会根据收集到的响应式依赖,在变量发生改变时就会被触发。

接下来看官方的描述:

wactchEffect:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

类型

 function watchEffect(
   effect: (onCleanup: OnCleanup) => void,
   options?: WatchEffectOptions
 ): StopHandle
 ​
 type OnCleanup = (cleanupFn: () => void) => void
 ​
 interface WatchEffectOptions {
   flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
   onTrack?: (event: DebuggerEvent) => void
   onTrigger?: (event: DebuggerEvent) => void
 }
 ​
 type StopHandle = () => void

基本使用

下面例子中watchEffect中只有name是响应式对像,它会在页面初始化的时候就执行一次用于收集name的响应式依赖,changeName事件被触发时,name被改变了,对应的就会触发watchEffect;当changeAge触发时,因为并没有在watchEffect中使用age,所以watchEffect没有收集到对应的响应式依赖,watchEffect就不会被触发。

 <template>
   <div id="app">
     <h2>{{ name }}</h2>
     <h2>{{ age }}</h2>
     <button @click="changeName">修改name</button>
     <button @click="changeAge">修改Age</button>
   </div>
 </template>
 ​
 <script>
 import { watchEffect, defineComponent, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
     //watchEffect:自动收集响应式依赖,默认初始化就会执行一次
     const name = ref("李四")
     const age = ref(18)
 ​
     watchEffect(() => {
       console.log("name:", name.value);
     })
     const changeName = () => name.value += "1"
     const changeAge = () => age.value += 1
 ​
     return {
       name,
       age,
       changeName,
       changeAge
     }
   },
 });
 </script>
 ​
 <style scoped></style>

停止监听

watchEffect会返回一个函数,这个函数可以用于停止对响应式对象的监听,下面例子中当age > 25是就会停止监听:

 <template>
   <div id="app">
     <h2>{{ name }}</h2>
     <h2>{{ age }}</h2>
     <button @click="changeName">修改name</button>
     <button @click="changeAge">修改Age</button>
   </div>
 </template>
 ​
 <script>
 import { watchEffect, defineComponent, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
     //watchEffect:自动收集响应式依赖,默认初始化就会执行一次
     const name = ref("李四")
     const age = ref(18)
 ​
     // wacthEffect会返回一个函数,这个函数可用于停止所有的wacthEffect的侦听
     const stop = watchEffect(() => {
       console.log("userInfo:", name.value,age.value);
     })
 ​
     const changeName = () => name.value += "1"
     const changeAge = () => {
       age.value += 1
       // 当 age > 25 时停止侦听
       if(age.value > 25) stop()
     }
 ​
     return {
       name,
       age,
       changeName,
       changeAge
     }
   },
 });
 </script>
 ​
 <style scoped></style>

清除副作用

在使用监听的时候我们可能会向服务器发送请求,当监听的数据频繁变化时,这种请求就会频繁触发,这无疑极大的浪费了服务器性能。watchEffect第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调,下面是官方给的例子:

 watchEffect(async (onCleanup) => {
   const { response, cancel } = doAsyncWork(id.value)
   // `cancel` 会在 `id` 更改时调用
   // 以便取消之前
   // 未完成的请求
   onCleanup(cancel)
   data.value = await response
 })

执行时机

有时候我们需要去监听dome的变化,通过ref拿到的domewatchEffect第一次执行时是null,这是因为此时dome还未渲染完成。watchEffec的第二个参数是一个可选项,其中flush可以用来调整watchEffect执行时机。

下面是官方对flush的描述:

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

举个栗子:

默认’pre‘:侦听器会在组件渲染前执行,控制台会输出两次,第一次为null,第二次是页面渲染完成成功获取到组件的时候,会输出组件的引用:

 <template>
   <div id="app">
     <h2 ref="name">张三</h2>
   </div>
 </template>
 ​
 <script>
 import { watchEffect, defineComponent, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
       // 执行时机(flush):'pre' | 'post' | 'sync' // 默认'pre'
       const name = ref(null)
       watchEffect(() => {
           console.log("nameDome:", name.value);
       })
     return {
       name
     }
   },
 });
 </script>
 ​
 <style scoped></style>

执行结果截图如下: 我们可以在控制台上看到wathcEffect在渲染完成之前执行了一次,此时的namenull,当渲染完成之后name的值发生了改变,watchEffect再次执行,输出这个节点:

修改为flush: 'post':它将会使侦听器延迟到组件渲染之后再执行。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。所以控制台只会输出一次,输出的是组件的引用:

 <template>
   <div id="app">
     <h2 ref="name">张三</h2>
   </div>
 </template>
 ​
 <script>
 import { watchEffect, defineComponent, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
       // 执行时机(flush):'pre' | 'post' | 'sync' // 默认'pre'
       const name = ref(null)
       watchEffect(() => {
           console.log("nameDome:", name.value);
       }, {
         flush: 'post'
       })
 ​
     return {
       name
     }
   },
 });
 </script>
 ​
 <style scoped></style>

执行结果截图如下: ,延后执行之后就不会触发一次无意义的监听了

watch

watch是一个侦听器,默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。先贴官方文档

  • watch需要侦听特定的数据源,并在回调函数中执行副作用;
  • 默认情况下watch是懒监听,只有在被监听的数据源发生变化的时候才会执行回调;

watch与watchEffect的区别

  • watch默认不会初始化立即执行;
  • watch有更具体的说明那些状态发生变化是触发侦听器的执行;
  • watch能够访问侦听状态变化前后的值;

类型参数

下面是官网对watch类型的描述:

 // 侦听单个来源
 function watch<T>(
   source: WatchSource<T>,
   callback: WatchCallback<T>,
   options?: WatchOptions
 ): StopHandle
 ​
 // 侦听多个来源
 function watch<T>(
   sources: WatchSource<T>[],
   callback: WatchCallback<T[]>,
   options?: WatchOptions
 ): StopHandle

watch一共有三个参数,分别是:sourcecallbackoptionsoptions为可选参数。

soure

soure是一个WatchSource<T>类型,该类型规定了soure可以是ref对象、reactive对象、数组对象、函数(getter)和普通对象:

 type WatchSource<T> =
   | Ref<T> // ref
   | (() => T) // getter
   | T extends object
   ? T
   : never // 响应式对象

callback

watch的第二个参数callbackwatch执行的回调,这个函数有三个参数,分别是vaule(新值)、oldValue(旧值)、onCleanup函数(用于清除副作用),下面是官网对于watch回调函数的描述:

 type WatchCallback<T> = (
   value: T,
   oldValue: T,
   onCleanup: (cleanupFn: () => void) => void
 ) => void

options

options是可选配置项。我们通过下面接口的描述看到它是继承至WatchEffectOptions的。immediate可以控制watch在组件初始化是是否执行,默认值是false

deep是控制是否开启深度监听的参数,watch在监听杂的对象时只对表层进行监听,默认值是false,如果对象的属性还是一个对像,那么这个对象只要地址不改变watch是不会触发的,通过deep: true可以监听到深层对象的改变,需要注意的是:1、当watch监听的是一个reactive对象时会自动开启深度监听;2、如果回调函数是因为深度监听的变更而触发的,那么valueoldValue将会是同一个对象。

flushwatchEffect一样的flush,用于控制触发实机,默认pre,在组件渲染之前执行,下面是官网的描述:

 interface WatchOptions extends WatchEffectOptions {
   immediate?: boolean // 默认:false
   deep?: boolean // 默认:false
   flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
   onTrack?: (event: DebuggerEvent) => void
   onTrigger?: (event: DebuggerEvent) => void
 }

基本使用

我将通过source传参的不同来举例watch的基本使用:

传入ref响应式对象

当监听的是ref对象时,callbackvalueoldValue获得的是ref对象对应的value值:

 <template>
   <div id="app">
     <h2>{{ name }}</h2>
     <button @click="changeName">改变用户数据</button>
   </div>
 </template>
 ​
 <script>
 import { watch, defineComponent, reactive, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
      //  传入ref对象,newValue和oldValue是对应的value值
      const name = ref('张三')
      watch(name, (newVlaue,oldValue) => {
        console.log('name:',newVlaue,oldValue);
      })
     const changeName = ()=>{
       name.value += "1"
     }
     return {
       name,
       changeName
     }
   },
 });
 </script>
 ​
 <style scoped></style>

侦听多个来源,callback会接收两个数组,对应的顺序是侦听数组的顺序,为了更直观我做了解构,也可以写成(newValue,oldValue)的形式:

 <template>
   <div id="app">
     <h2>{{ name }}</h2>
     <h2>{{ age }}</h2>
     </h2>
     <button @click="changeInfo">改变用户数据</button>
   </div>
 </template>
 ​
 <script>
 import { watch, defineComponent, reactive, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
      //  传入多个对象,newValue和oldValue是对应的value值
      const name = ref('张三')
      const age = ref(18)
      watch([name,age], ([newName,newAge],[oldName,oldAge]) => {
        console.log('new:',newName,newAge,'old',oldName,oldAge);
      })
     const changeInfo = ()=>{
       name.value += "1"
       age.value += 1
     }
     return {
       name,
       age,
       changeInfo
     }
   },
 });
 </script>
 ​
 <style scoped></style>

传入reactive对象,callback对应的valueoldValue都将是reactive对象,下面userInfo是一个reactive对象,所以newValueoldValue都会是reactive对象:

 <template>
   <div id="app">
     <h2>{{ userInfo.name }}</h2>
     <h2>{{ userInfo.age }}</h2>
     <button @click="changeInfo">改变用户数据</button>
   </div>
 </template>
 ​
 <script>
 import { watch, defineComponent, reactive, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
     const userInfo = reactive({ name: '张三', age: 18 })
     // 传入reactive对象
      watch(userInfo, (newValue, oldValue) => {
          console.log('userInfo',newValue,oldValue);
      })
     const changeInfo = ()=>{
       userInfo.name += "1"
       userInfo.age += 1
     }
     return {
       userInfo,
       changeInfo
     }
   },
 });
 </script>
 ​
 <style scoped></style>

如果我们不希望得到响应式的newValueoldValue,那么我们可以使用getter函数传参方式对reactive进行解构:

 <template>
   <div id="app">
     <h2>{{ userInfo.name }}</h2>
     <h2>{{ userInfo.age }}</h2>
     <button @click="changeInfo">改变用户数据</button>
   </div>
 </template>
 ​
 <script>
 import { watch, defineComponent, reactive, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
     const userInfo = reactive({ name: '张三', age: 18 })
     // 如果不希望newValue和oldValue是reactive对象可以在传入时对它进行解构
     watch(() => { return {...userInfo} }, (newValue, oldValue) => {
       console.log('userInfo:',newValue,oldValue);
     })
     const changeInfo = ()=>{
       userInfo.name += "1"
       userInfo.age += 1
     }
     return {
       userInfo,
       changeInfo
     }
   },
 });
 </script>
 ​
 <style scoped></style>

传入getter函数,下面摘自官网的描述:"当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。"

 <template>
   <div id="app">
     <h2>{{ userInfo.name }}</h2>
     <h2>{{ userInfo.age }}</h2>
     <button @click="changeInfo">改变用户数据</button>
   </div>
 </template>
 ​
 <script>
 import { watch, defineComponent, reactive, ref } from "vue";
 ​
 export default defineComponent({
   setup() {
     const userInfo = reactive({ name: '张三', age: 18 })
 ​
     // 二、传入getter函数形式
     watch(() => userInfo.name, (newVlaue,oldValue) => {
       console.log('newValue',newVlaue,'oldValue',oldValue)
     })
 ​
     const changeInfo = ()=>{
       userInfo.name += "1"
       userInfo.age += 1
     }
     return {
       userInfo,
       changeInfo
     }
   },
 });
 </script>
 ​
 <style scoped></style>

以上就是Vue3中watchEffect和watch的基础应用详解的详细内容,更多关于Vue3 watchEffect和watch的资料请关注脚本之家其它相关文章!

相关文章

  • VSCode Vue开发推荐插件和VSCode快捷键(小结)

    VSCode Vue开发推荐插件和VSCode快捷键(小结)

    这篇文章主要介绍了VSCode Vue开发推荐插件和VSCode快捷键(小结),文中通过图文表格介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • vue3 Class 与 Style 绑定操作方法

    vue3 Class 与 Style 绑定操作方法

    数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式,因为 class 和 style 都是 attribute,我们可以和其他 attribute 一样使用 v-bind 将它们和动态的字符串绑定,这篇文章主要介绍了vue3 Class 与 Style 绑定操作方法,需要的朋友可以参考下
    2024-05-05
  • 项目部署后前端vue代理失效问题解决办法

    项目部署后前端vue代理失效问题解决办法

    这篇文章主要给大家介绍了关于项目部署后前端vue代理失效问题的解决办法,文中通过图文以及代码示例将解决的办法介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-02-02
  • Vue获取子组件实例对象ref属性的方法推荐

    Vue获取子组件实例对象ref属性的方法推荐

    在Vue中我们可以使用ref属性来获取子组件的实例对象,这个功能非常方便,这篇文章主要给大家介绍了关于Vue获取子组件实例对象ref属性的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • vue嵌入本地iframe文件并获取某元素的值方式

    vue嵌入本地iframe文件并获取某元素的值方式

    这篇文章主要介绍了vue嵌入本地iframe文件并获取某元素的值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • vue+node+webpack环境搭建教程

    vue+node+webpack环境搭建教程

    这篇文章主要为大家详细介绍了vue+node+webpack环境搭建教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 用electron 打包发布集成vue2.0项目的操作过程

    用electron 打包发布集成vue2.0项目的操作过程

    这篇文章主要介绍了用electron 打包发布集成vue2.0项目的操作步骤,把electron 加入到自己项目中各种不兼容,升级版本踩坑无数个,今天通过本文给大家分享下详细过程,需要的朋友可以参考下
    2022-10-10
  • Vue3实现PDF文件解析与预览的完整实践

    Vue3实现PDF文件解析与预览的完整实践

    在实际的前端开发中,经常会碰到需要在线预览PDF文件的场景,比如后台管理系统查看合同、教育平台展示试卷、审批系统预览发票等等,所以本文给大家介绍了Vue3实现PDF文件解析与预览的完整实践,需要的朋友可以参考下
    2025-06-06
  • vue实现将一维数组变换为三维数组

    vue实现将一维数组变换为三维数组

    这篇文章主要为大家详细介绍了vue如何实现将一维数组变换为三维数组,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-01-01
  • 一篇文章让你看懂封装Axios

    一篇文章让你看懂封装Axios

    axios的封装和api接口的统一管理,其实主要目的就是在帮助我们简化代码和利于后期的更新维护,这篇文章主要给大家介绍了关于封装Axios的相关资料,需要的朋友可以参考下
    2022-01-01

最新评论