【Vue3 第八章】watch 系列侦听器

简介: 【Vue3 第八章】watch 系列侦听器

数字化管理平台

Vue3+Vite+VueRouter+Pinia+Axios+ElementPlus

权限系统-商城

个人博客地址

一、watch 侦听器概述

计算属性允许我们声明性地计算衍生值,但是不建议在计算属性中去修改状态或更改DOM。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态,这就用到了 watch 侦听器。

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

<script setup>
    import { ref, watch } from 'vue'
    const question = ref('')
    const answer = ref('Questions usually contain a question mark. ;-)')
    // 可以直接侦听一个 ref
    watch(question, async (newQuestion, oldQuestion) => {
      if (newQuestion.indexOf('?') > -1) {
        answer.value = 'Thinking...'
        try {
          const res = await fetch('https://yesno.wtf/api')
          answer.value = (await res.json()).answer
        } catch (error) {
          answer.value = 'Error! Could not reach the API. ' + error
        }
      }
    })
</script>
<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>

接口 https://yesno.wtf/api 返回的数据如下:

{
    answer: "no",
    forced: false,
    image: "https://yesno.wtf/assets/no/10-d5ddf3f82134e781c1175614c0d2bab2.gif"
}

二、watch 侦听数据源类型

2.1 概述

watch() 函数用于侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。 watch函数接收的参数如下:

  • 第一个参数:侦听器的源。这个来源可以是以下几种:
  • 一个 getter 函数,返回一个值

一个 ref (包括计算属性computed定义)

一个响应式对象(reactive定义)

由以上类型的数据源组成的数组

第二个参数:在发生变化时要调用的回调函数 cb(newVal,oldVal,callback)

这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个参数:options配置项(一个对象)

immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。

deep:如果源是对象或数组,则强制深度遍历源,以便在深层级变更时触发回调。参考深层侦听器。

提示:深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。

flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。

值默认为{ flush: 'pre' },即vue组件更新前触发;若设置为{ flush: 'post' }则回调将在vue组件更新之后触发;此外还可以设置为{ flush: 'sync' },表示同步触发。

onTrack / onTrigger:调试侦听器的依赖关系。

设置收集依赖时的onTrack和触发更新时的onTrigger两个listener,主要用于debugger (调试 watch 用)。

{
  immediate:true, //是否立即调用一次
  deep:true //是否开启深度监听,
  onTrigger(){
  }
}

2.2 示例代码

<script setup>
  import { ref, reactive, watch } from 'vue'
  // 监听 ref 数据源
  let name = ref("MagnumHou")
  let obj = ref({
      name: "Magnum",
      age: 23,
      sex: "male"
  })
  // 定义监听
  watch(name, (newName, oldName) => {
      console.log("newName:", newName, "oldName:", oldName, "name:", name.value)
  })
  watch(obj, (newVal, oldVal) => {
      // 当通过watch去监听复杂数据类型时,数据发生改变后,监听到的 newVal和 oldVal 都是最新的值
      console.log("newVal:", newVal, "oldVal:", oldVal);
  }, {
      deep: true //深度监听
  })
  // 监听 reactive 数据源
  let obj2 = reactive({
      foo: {
          bar: {
              num: 1
          }
      }
  })
  watch(obj2, (newObj, oldObj) => {
      console.log("newObj:", newObj, "oldObj:", oldObj)
  })
  // 通过reactive定义的复杂的数据类型,会自动开启deep:true深度监听
  // 监听对象中的某个属性(监听单一属性)
  let stu = reactive({
      name: "Jack",
      age: 18,
      scores: {
          chinese: 100,
          english: 60,
          japanese: 150
      }
  })
  // 监听单一属性:需要采用 get 函数,将对应的属性返回。不能直接打点的方式去做数据监听
  watch(() => stu.name, (newName, oldName) => {
      console.log("newName:", newName, "olsName:", oldName)
  })
  watch(() => stu.scores.chinese, (newScore, oldScore) => {
      console.log("newScore:", newScore, "oldScore:", oldScore)
  }, {
      immediate: true,//随着程序的加载立即执行一次,第一次执行 oldVal 是 undefined
      flush: "post",//组件加载后执行
  })
</script>
<template>
    <div>
        <h3>您最新的笔名:{{ name }}</h3>
        username:<input type="text" v-model.lazy="name">
        <br>
        age:<input type="text" v-model.lazy="obj.age">
        <br>
        num: <input type="text" v-model.lazy="obj2.foo.bar.num" />
        <br>
        stuName: <input type="text" v-model.lazy="stu.name">
        <br>
        chinese~score: <input type="text" v-model.lazy="stu.scores.chinese">
    </div>
</template>

三、watchEffect 高级侦听器

3.1 概述

watchEffect(callback,options) 用于立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。 函数的参数如下:

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

第二个参数是一个可选的选项(options 对象),可以用来调整副作用的刷新时机或调试副作用的依赖。同 watch 函数中 options 配置项。

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

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

<script setup>
  import { ref, reactive, watchEffect, watchPostEffect, watchSyncEffect } from 'vue'
  // 监听 ref 数据变化
  let name = ref("MagnumHou");
  watchEffect(() => {
      console.log("watchEffect中的name:", name)
  }, {
      flush: "post"
  })
  // flush:"post" 对应于一个钩子函数  watchPostEffect
  // flush:"sync" 对应于一个钩子函数  watchSyncEffect
  // 监听一个 reactive 对象
  let obj = reactive({
      foo: {
          bar: {
              num: 1
          }
      }
  })
  watchEffect((callback) => {
      console.log("watchEffect中的num2:", obj.foo.bar.num)
      // 这是一个清除副作用的函数 cleanFn,里面是一个回调函数;优先于侦听数据之前之前
      callback(() => { console.log("在其它watchEffect数据侦听之前调用....") })
  })
  setTimeout(() => {
      name.value = "Lucy"
      obj.foo.bar.num = 1000
  }, 5000)
</script>

3.2 watch 与 watchEffect 区别

watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:

watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。

watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。

四、停止侦听器

在 setup() 或 <script setup> 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。

一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。

<script setup>
    import { watchEffect } from 'vue'
    // 它会自动停止
    watchEffect(() => {})
    // ...这个则不会!
    setTimeout(() => {
      watchEffect(() => {})
    }, 100)
</script>

要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数

const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()

注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑

// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
  if (data.value) {
    // 数据加载后执行某些操作...
  }
})
相关文章
|
28天前
|
开发工具 iOS开发 MacOS
基于Vite7.1+Vue3+Pinia3+ArcoDesign网页版webos后台模板
最新版研发vite7+vue3.5+pinia3+arco-design仿macos/windows风格网页版OS系统Vite-Vue3-WebOS。
212 11
|
5月前
|
缓存 JavaScript PHP
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
820 5
|
12天前
|
JavaScript 安全
vue3使用ts传参教程
Vue 3结合TypeScript实现组件传参,提升类型安全与开发效率。涵盖Props、Emits、v-model双向绑定及useAttrs透传属性,建议明确声明类型,保障代码质量。
109 0
|
2月前
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
331 1
|
2月前
|
缓存 JavaScript UED
除了循环引用,Vue3还有哪些常见的性能优化技巧?
除了循环引用,Vue3还有哪些常见的性能优化技巧?
174 0
|
3月前
|
JavaScript
vue3循环引用自已实现
当渲染大量数据列表时,使用虚拟列表只渲染可视区域的内容,显著减少 DOM 节点数量。
109 0
|
5月前
|
JavaScript 前端开发 API
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
1559 0
|
1月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
200 2
|
4月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
636 0
|
4月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能