当前位置: 首页 > ds >正文

vue3学习——侦听器

一、侦听器的基本概念

计算属性允许我们声明性地计算衍生值,但在一些场景下,比如状态变化时执行副作用(像更改 DOM、根据异步操作结果修改其他状态),就需要用到侦听器。简单来说,侦听器就像一个 “数据变化监控器”,当它 “观察” 的数据发生变化,就会执行我们预先设定的代码。

二、选项式 API 中的侦听器

在选项式 API 里,我们使用watch选项来创建侦听器。示例代码如下:

export default {data() {return {question: '',answer: 'Questions usually contain a question mark. ;-)',loading: false}},watch: {// 每当question改变时,这个函数就会执行question(newQuestion, oldQuestion) {if (newQuestion.includes('?')) {this.getAnswer()}}},methods: {async getAnswer() {this.loading = truethis.answer = 'Thinking...'try {const res = await fetch('https://yesno.wtf/api')this.answer = (await res.json()).answer} catch (error) {this.answer = 'Error! Could not reach the API. ' + error} finally {this.loading = false}}}
}

在上述代码中,watch选项监听question数据的变化。一旦question发生改变,就会执行对应的函数。在函数内部,检查新的question是否包含?,如果包含,则调用getAnswer方法。getAnswer方法用于发起异步请求获取答案,并处理请求过程中的加载状态和错误情况。

watch选项还支持监听对象的嵌套属性,通过用.分隔的路径来设置键,如:

export default {watch: {// 注意:只能是简单的路径,不支持表达式。'some.nested.key'(newValue) {//...}}
}

三、组合式 API 中的侦听器

在组合式 API 中,我们使用watch函数来创建侦听器。示例如下:

<script setup>
import { ref, watch } from 'vue'const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)// 可以直接侦听一个ref
watch(question, async (newQuestion, oldQuestion) => {if (newQuestion.includes('?')) {loading.value = trueanswer.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} finally {loading.value = false}}
})
</script><template><p>Ask a yes/no question:<input v-model="question" :disabled="loading" /></p><p>{{ answer }}</p>
</template>

这里通过watch函数监听question这个ref,当question变化时,执行回调函数,逻辑和选项式 API 中的类似。

(一)侦听数据源类型

watch函数的第一个参数可以是多种形式的 “数据源”:

  1. 单个 ref:可以直接侦听一个ref,如watch(x, (newX) => { console.log(x is ${newX}) })
  2. getter 函数:通过返回一个值的函数来侦听,如watch(() => x.value + y.value, (sum) => { console.log(sum of x + y is: ${sum}) }) 。
  3. 多个数据源组成的数组:同时侦听多个数据源,如watch([x, () => y.value], ([newX, newY]) => { console.log(x is ${newX} and y is ${newY}) }) 。

注意,不能直接侦听响应式对象的属性值,比如const obj = reactive({ count: 0 }); watch(obj.count, (count) => { console.log(Count is: ${count}) })这样是错误的,需要使用返回该属性的getter函数,即watch(() => obj.count, (count) => { console.log(Count is: ${count}) }) 。

(二)深层侦听器

默认情况下,watch是浅层侦听,即被侦听的属性只有在被赋新值时才会触发回调,嵌套属性的变化不会触发。若要侦听所有嵌套的变更,需要使用深层侦听器。
在选项式 API 中,可以这样设置:

export default {watch: {someObject: {handler(newValue, oldValue) {// 注意:在嵌套的变更中,只要没有替换对象本身,那么这里的`newValue`和`oldValue`相同},deep: true}}
}

在组合式 API 中,直接给watch()传入一个响应式对象,会隐式创建深层侦听器:

const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {// 在嵌套的属性变更时触发// 注意:`newValue`此处和`oldValue`是相等的,因为它们是同一个对象!
})
obj.count++

如果是返回响应式对象的 getter 函数,默认只有在返回不同对象时才会触发回调,若要强制转成深层侦听器,需显式加上deep选项:

watch(() => state.someObject,(newValue, oldValue) => {// 注意:`newValue`此处和`oldValue`是相等的,*除非* state.someObject被整个替换了},{ deep: true }
)

注意:深度侦听需要遍历被侦听对象的所有嵌套属性,用于大型数据结构时开销较大,要谨慎使用。

(三)即时回调的侦听器

watch默认是懒执行的,仅当数据源变化时才执行回调。但有些场景下,我们希望在创建侦听器时立即执行一遍回调,比如请求初始数据。
在选项式 API 中,可以用包含handler方法和immediate: true选项的对象来声明侦听器:

export default {//...watch: {question: {handler(newQuestion) {// 在组件实例创建时会立即调用},// 强制立即执行回调immediate: true}}//...
}

在组合式 API 中,通过传入immediate: true选项来强制回调立即执行:

watch(source,(newValue, oldValue) => {// 立即执行,且当`source`改变时再次执行},{ immediate: true }
)

(四)一次性侦听器(仅支持 3.4 及以上版本)

如果希望侦听器的回调只在源变化时触发一次,可以使用once: true选项。
在选项式 API 中:

export default {watch: {source: {handler(newValue, oldValue) {// 当`source`变化时,仅触发一次},once: true}}
}

在组合式 API 中:

watch(source,(newValue, oldValue) => {// 当`source`变化时,仅触发一次},{ once: true }
)

四、watchEffect()函数

watchEffect()函数可以自动跟踪回调的响应式依赖。例如,用watch函数加载远程资源的代码:

const todoId = ref(1)
const data = ref(null)
watch(todoId,async () => {const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)data.value = await response.json()},{ immediate: true }
)

使用watchEffect()函数可以简化为:

watchEffect(async () => {const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)data.value = await response.json()
})

这里回调会立即执行,不需要指定immediate: true,并且会自动追踪todoId.value作为依赖,每当todoId.value变化时,回调会再次执行。对于有多个依赖项的侦听器,使用watchEffect()可以避免手动维护依赖列表;在侦听嵌套数据结构中的几个属性时,watchEffect()可能比深度侦听器更高效,因为它只跟踪回调中使用到的属性。

注意watchEffect仅在其同步执行期间追踪依赖,使用异步回调时,只有在第一个await正常工作前访问到的属性才会被追踪。

(一)watchwatchEffect的区别

watchwatchEffect都能响应式地执行有副作用的回调,但追踪响应式依赖的方式不同:

  • watch只追踪明确侦听的数据源,不会追踪回调中访问到的其他东西,仅在数据源确实改变时才触发回调,能更精确地控制回调函数的触发时机。
  • watchEffect会在副作用发生期间追踪依赖,在同步执行过程中自动追踪所有能访问到的响应式属性,代码更简洁,但响应性依赖关系可能不那么明确。

五、副作用清理

在侦听器执行异步操作(如异步请求)时,如果在请求完成前数据发生变化,可能会导致使用过时数据。这时可以使用清理函数来解决这个问题。
在 Vue 3.5 + 版本中,可以使用onWatcherCleanup()API 来注册清理函数,在侦听器失效并准备重新运行时被调用:

import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {const controller = new AbortController()fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {// 回调逻辑})onWatcherCleanup(() => {// 终止过期请求controller.abort()})
})

在 3.5 之前的版本,以及在 Vue 3.5 + 版本中,还可以通过将onCleanup函数作为第三个参数传递给侦听器回调和watchEffect作用函数的第一个参数来实现清理功能:

watch(id, (newId, oldId, onCleanup) => {//...onCleanup(() => {// 清理逻辑})
})
watchEffect((onCleanup) => {//...onCleanup(() => {// 清理逻辑})
})

六、回调的触发时机

当更改响应式状态时,可能会同时触发 Vue 组件更新和侦听器回调。默认情况下,侦听器回调会在父组件更新(如有)之后、所属组件的 DOM 更新之前被调用,此时在侦听器回调中访问所属组件的 DOM,得到的是更新前的状态。

(一)Post Watchers

如果想在侦听器回调中访问被 Vue 更新之后的所属组件的 DOM,需要指明flush: 'post'选项。
在选项式 API 中:

export default {//...watch: {key: {handler() {},flush: 'post'}}
}

在组合式 API 中:

watch(source, callback, {flush: 'post'
})
watchEffect(callback, {flush: 'post'
})

后置刷新的watchEffect()有个更方便的别名watchPostEffect()

import { watchPostEffect } from 'vue'
watchPostEffect(() => {/* 在Vue更新后执行 */
})

(二)同步侦听器

还可以创建同步触发的侦听器,它会在 Vue 进行任何更新之前触发。
在选项式 API 中:

export default {//...watch: {key: {handler() {},flush: 'sync'}}
}

同步触发的watchEffect()有个更方便的别名watchSyncEffect()

import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {/* 在响应式数据变化时同步执行 */
})

注意:同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发,适合监视简单的布尔值,应避免在可能多次同步修改的数据源(如数组)上使用。

七、this.$watch()方法

在组件实例中,还可以使用this.$watch()方法命令式地创建一个侦听器:

export default {created() {this.$watch('question', (newQuestion) => {//...})}
}

这种方式在特定条件下设置侦听器,或者只侦听响应用户交互的内容时很有用,并且可以提前停止该侦听器。

八、停止侦听器

watch选项或者this.$watch()实例方法声明的侦听器,会在宿主组件卸载时自动停止。但在少数情况下,需要在组件卸载前手动停止侦听器,可以调用this.$watch()API 返回的函数:

const unwatch = this.$watch('foo', callback)
//...当该侦听器不再需要时
unwatch()

setup()<script setup>中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,在宿主组件卸载时自动停止。但如果用异步回调创建侦听器,它不会绑定到当前组件上,必须手动停止,否则可能造成内存泄漏。例如:

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

手动停止侦听器的方法是调用watchwatchEffect返回的函数:

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

尽量选择同步创建侦听器,如果需要等待异步数据,可以使用条件式的侦听逻辑,如:

// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {if (data.value) {// 数据加载后执行某些操作...}
})

http://www.xdnf.cn/news/5948.html

相关文章:

  • 从零开始掌握FreeRTOS——目录
  • Java后端快速生成验证码
  • Python查询ES错误ApiError(406, ‘Content-Type ...is not supported
  • vr视频制作攻略(VR视频制作基础知识)
  • 漏桶算法的实际应用案例:数据库批量写入流量控制
  • 基于智能家居项目 解析DHT11温湿度传感器
  • hadoop中创建MySQL新数据库数据表
  • 数据库数据清洗、预处理与质量监控、 数据质量的核心概念
  • 《Effective Python》第1章 Pythonic 思维总结——编写优雅、高效的 Python 代码
  • 分布式任务调度XXL-Job
  • STM32 __main
  • 项目:博客系统——基于SSM框架Mybatis-plus
  • C++学习之路,从0到精通的征途:继承
  • hadoop3.x单机部署
  • 【计算机网络 第8版】谢希仁编著 第四章网络层 题型总结3 SDN OpenFlow
  • 工程师必读! 3 个最常被忽略的 TDR 测试关键细节与原理
  • ubuntu20.04安装qtcreator并打开ros工程
  • CD3MN 双相钢 2205 材质保温 V 型球阀:恒温工况下复杂介质控制的高性能之选-耀圣
  • 72.编辑距离
  • 11. CSS从基础样式到盒模型与形状绘制
  • KV cache 缓存与量化:加速大型语言模型推理的关键技术
  • AUTOSAR图解==>AUTOSAR_TPS_FeatureModelExchangeFormat
  • 榕壹云搭子系统技术解析:基于Spring Boot+MySQL+UniApp的同城社交平台开发实践
  • 国内USB IP商业解决方案新选择:硬件USB Server
  • 鸿蒙Next开发 获取APP缓存大小和清除缓存
  • 图片的require问题
  • 轻量级高性能推理引擎MNN 学习笔记 02.MNN主要API
  • 【工作记录】Kong Gateway入门篇之简介
  • 短板效应--双指针
  • ElasticSearch深入解析(十一):分页