computed()、watch() 与 watchEffect()

下面,我们来系统的梳理关于 computed、watch 与 watchEffect 的基本知识点:


一、核心概念与响应式基础

1.1 响应式依赖关系

Vue 的响应式系统基于 依赖收集触发更新 的机制:

响应式数据
依赖收集
创建依赖关系
数据变更
触发更新
执行副作用

1.2 三大 API 对比

特性computedwatchwatchEffect
返回值Ref 对象停止函数停止函数
依赖收集自动手动指定自动
执行时机惰性求值响应变化立即执行
主要用途派生状态响应变化执行操作自动追踪副作用
新旧值提供新旧值
异步支持同步支持异步支持异步
首次执行访问时执行可配置总是执行

二、computed 深度解析

2.1 基本使用与类型

import { ref, computed } from 'vue'// 只读计算属性
const count = ref(0)
const double = computed(() => count.value * 2)// 可写计算属性
const fullName = computed({get: () => `${firstName.value} ${lastName.value}`,set: (newValue) => {[firstName.value, lastName.value] = newValue.split(' ')}
})

2.2 实现原理

用户代码 computed() ReactiveEffect Dep 数据变更 用户再次访问 创建计算属性 创建 ReactiveEffect 收集依赖 访问 .value 执行计算函数 追踪依赖 返回计算结果 触发更新 标记为脏值 重新计算 用户代码 computed() ReactiveEffect Dep 数据变更 用户再次访问

2.3 核心特性

  1. 惰性求值:仅在访问 .value 时计算
  2. 结果缓存:依赖未变化时返回缓存值
  3. 依赖追踪:自动收集响应式依赖
  4. 类型安全:完美支持 TypeScript 类型推断

2.4 最佳实践

// 避免在计算属性中产生副作用
const badExample = computed(() => {console.log('This is a side effect!') // 避免return count.value * 2
})// 复杂计算使用计算属性
const totalPrice = computed(() => {return cartItems.value.reduce((total, item) => {return total + (item.price * item.quantity)}, 0)
})// 组合多个计算属性
const discountedTotal = computed(() => {return totalPrice.value * (1 - discountRate.value)
})

三、watch 深度解析

3.1 基本使用与语法

import { watch, ref } from 'vue'// 侦听单个源
const count = ref(0)
watch(count, (newValue, oldValue) => {console.log(`Count changed: ${oldValue}${newValue}`)
})// 侦听多个源
const firstName = ref('John')
const lastName = ref('Doe')
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {console.log(`Name changed: ${oldFirst} ${oldLast}${newFirst} ${newLast}`)
})// 深度侦听对象
const state = reactive({ user: { name: 'Alice' } })
watch(() => state.user,(newUser, oldUser) => {console.log('User changed', newUser, oldUser)},{ deep: true }
)

3.2 配置选项详解

watch(source, callback, {// 立即执行回调immediate: true,// 深度侦听deep: true,// 回调执行时机flush: 'post', // 'pre' | 'post' | 'sync'// 调试钩子onTrack: (event) => debugger,onTrigger: (event) => debugger
})

3.3 高级用法

// 异步操作与取消
const data = ref(null)
watch(id, async (newId, oldId, onCleanup) => {const controller = new AbortController()onCleanup(() => controller.abort())try {const response = await fetch(`/api/data/${newId}`, {signal: controller.signal})data.value = await response.json()} catch (error) {if (error.name !== 'AbortError') {console.error('Fetch error:', error)}}
})// 限制执行频率
import { throttle } from 'lodash-es'
watch(searchQuery,throttle((newQuery) => {search(newQuery)}, 500)
)

3.4 性能优化

// 避免深度侦听大型对象
watch(() => state.items.length, // 仅侦听长度变化(newLength) => {console.log('Item count changed:', newLength)}
)// 使用浅层侦听
watch(() => ({ ...shallowObject }), // 创建浅拷贝(newObj) => {console.log('Shallow object changed')}
)

四、watchEffect 深度解析

4.1 基本使用

import { watchEffect, ref } from 'vue'const count = ref(0)// 自动追踪依赖
const stop = watchEffect((onCleanup) => {console.log(`Count: ${count.value}`)// 清理副作用onCleanup(() => {console.log('Cleanup previous effect')})
})// 停止侦听
stop()

4.2 核心特性

  1. 自动依赖收集:无需指定侦听源
  2. 立即执行:创建时立即运行一次
  3. 清理机制:提供 onCleanup 回调
  4. 异步支持:天然支持异步操作

4.3 高级用法

// DOM 操作副作用
const elementRef = ref(null)
watchEffect(() => {if (elementRef.value) {// 操作 DOMelementRef.value.focus()}
})// 响应式日志
watchEffect(() => {console.log('State updated:', {count: count.value,user: user.value.name})
})// 组合多个副作用
watchEffect(async () => {const data = await fetchData(params.value)processData(data)
})

4.4 性能优化

// 使用 flush 控制执行时机
watchEffect(() => {// 在 DOM 更新后执行updateChart()},{ flush: 'post' }
)// 调试依赖
watchEffect(() => {// 副作用代码},{onTrack(e) {debugger // 依赖被追踪时},onTrigger(e) {debugger // 依赖变更触发回调时}}
)

五、三者的区别与选择指南

5.1 使用场景对比

场景推荐 API理由
派生状态computed自动缓存,高效计算
数据变化响应watch精确控制,获取新旧值
副作用管理watchEffect自动依赖收集
异步操作watch/watchEffect支持异步和取消
DOM 操作watchEffect自动追踪 DOM 依赖
调试依赖watch精确控制侦听源

5.2 性能考虑

  1. computed:适合同步计算,避免复杂操作
  2. watch:适合需要精确控制的场景
  3. watchEffect:适合自动依赖收集的副作用
// 计算属性 vs 侦听器
// 推荐:使用计算属性派生状态
const fullName = computed(() => `${firstName.value} ${lastName.value}`)// 不推荐:使用侦听器模拟计算属性
const fullName = ref('')
watch([firstName, lastName], () => {fullName.value = `${firstName.value} ${lastName.value}`
})

5.3 组合使用模式

// 组合 computed 和 watch
const discountedTotal = computed(() => total.value * (1 - discount.value))watch(discountedTotal, (newTotal) => {updateUI(newTotal)
})// 组合 watchEffect 和 computed
const searchResults = ref([])
const searchQuery = ref('')const validQuery = computed(() => searchQuery.value.trim().length > 2
)watchEffect(async (onCleanup) => {if (!validQuery.value) returnconst controller = new AbortController()onCleanup(() => controller.abort())searchResults.value = await fetchSearchResults(searchQuery.value, controller.signal)
})

六、原理深入剖析

6.1 Vue 响应式系统核心

订阅
1
*
使用
Dep
+depend()
+notify()
ReactiveEffect
+run()
+stop()
ComputedRefImpl
-_value
-_dirty
+get value()
+set value()

6.2 computed 实现原理

class ComputedRefImpl {constructor(getter, setter) {this._getter = getterthis._setter = setterthis._value = undefinedthis._dirty = truethis.effect = new ReactiveEffect(getter, () => {if (!this._dirty) {this._dirty = truetrigger(this, 'value')}})}get value() {if (this._dirty) {this._value = this.effect.run()this._dirty = falsetrack(this, 'value')}return this._value}set value(newValue) {this._setter(newValue)}
}

6.3 watch 和 watchEffect 的异同

实现机制watchwatchEffect
依赖收集基于指定源自动收集执行中的依赖
内部实现基于 watchEffect基础 API
调度机制支持 flush 配置支持 flush 配置
清理机制通过回调参数通过 onCleanup

七、最佳实践与性能优化

7.1 性能优化策略

  1. 避免不必要的重新计算

    // 使用 computed 缓存结果
    const filteredList = computed(() => largeList.value.filter(item => item.active)
    )
    
  2. 合理使用侦听选项

    // 减少深度侦听范围
    watch(() => state.user.id, // 仅侦听 ID 变化(newId) => fetchUser(newId)
    )
    
  3. 批量更新处理

    watch([data1, data2],() => {// 合并处理多个变化updateVisualization()},{ flush: 'post' }
    )
    

7.2 常见模式

数据获取模式

const data = ref(null)
const error = ref(null)watchEffect(async (onCleanup) => {data.value = nullerror.value = nullconst controller = new AbortController()onCleanup(() => controller.abort())try {const response = await fetch(url.value, {signal: controller.signal})data.value = await response.json()} catch (err) {if (err.name !== 'AbortError') {error.value = err.message}}
})

表单验证模式

const formState = reactive({ email: '', password: '' })
const errors = reactive({ email: '', password: '' })watch(() => [formState.email, formState.password],() => {errors.email = formState.email.includes('@') ? '' : 'Invalid email'errors.password = formState.password.length >= 6 ? '' : 'Too short'},{ immediate: true }
)

八、常见问题与解决方案

8.1 响应式依赖问题

问题: watchEffect 未正确追踪依赖

const state = reactive({ count: 0 })watchEffect(() => {// 当 state.count 变化时不会触发console.log(state.nested?.value) // 可选链导致依赖丢失
})

解决方案:

watchEffect(() => {// 显式访问确保依赖追踪if (state.nested) {console.log(state.nested.value)}
})

8.2 异步竞态问题

问题: 多个异步请求可能导致旧数据覆盖新数据

watch(id, async (newId) => {const data = await fetchData(newId)currentData.value = data // 可能旧请求覆盖新
})

解决方案:

watch(id, async (newId, _, onCleanup) => {let isCancelled = falseonCleanup(() => isCancelled = true)const data = await fetchData(newId)if (!isCancelled) {currentData.value = data}
})

8.3 无限循环问题

问题: 侦听器中修改依赖数据导致循环

watch(count, (newVal) => {count.value = newVal + 1 // 无限循环
})

解决方案:

// 添加条件判断
watch(count, (newVal, oldVal) => {if (newVal < 100) {count.value = newVal + 1}
})// 使用 watchEffect 替代
watchEffect(() => {if (count.value < 100) {count.value += 1}
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/bicheng/86336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux驱动开发 ---- 4.2_平台设备(Platform Devices)概述】

Linux驱动开发 ---- 4.2_平台设备&#xff08;Platform Devices&#xff09;概述 目录 Linux驱动开发 ---- 4.2_平台设备&#xff08;Platform Devices&#xff09;概述前述主要特点&#xff1a;平台设备的作用平台设备的注册与注销1. platform_device_register_simple()2. pla…

深入学习入门--(一)前备知识

一.Python基础知识 1.1 Python算数运算 1.2 变量 1.3 数据类型 1.3.1 int&#xff08;整数&#xff09; float&#xff08;浮点数&#xff09; str&#xff08;字符串&#xff09; 1.3.2 bool&#xff08;布尔值&#xff09;: 表示真或假 取值:True,False 1.3.3 list&…

iClone 中创建的面部动画导入 Daz 3D

以下是如何将 iClone 中创建的面部动画导入 Daz 3D 的简要指南。简而言之&#xff0c;您可以通过 FBX&#xff08;使用 3DXchange 或 Character Creator 的导出工具&#xff09;导出 iClone 面部动画&#xff0c;然后将其导入 Daz Studio 并将变形或骨骼重新映射到 Genesis 角色…

OceanBase向量检索在货拉拉的探索和实践

货拉拉成立于2013年&#xff0c;成长于粤港澳大湾区&#xff0c;是从事同城跨城货运、企业版物流服务、搬家、零担、跑腿、冷运、汽车租售及车后市场服务的互联网物流商城。截至2024年&#xff0c;货拉拉在全球拥有1670万月活用户和168万月活司机&#xff0c;业务覆盖全球11个市…

Flask(五) 表单处理 request.form、WTForms

文章目录 1. 基本表单处理&#xff0c;使用 request.form&#xff08;轻量&#xff09;示例一创建 HTML 表单处理表单数据 示例二HTML 表单&#xff08;login.html&#xff09;Flask 路由处理表单 2. 使用 Flask-WTF 扩展安装设置 Secret Key&#xff08;CSRF 防护&#xff09;…

c++虚继承复习

深入理解C虚继承&#xff1a;解决菱形继承问题的利器 在C面向对象编程中&#xff0c;多重继承是一个强大但容易误用的特性。今天我们来探讨一个特殊的多重继承形式——虚继承&#xff08;Virtual Inheritance&#xff09;&#xff0c;它是解决著名的"菱形继承问题"的…

魔乐社区国产算力应用创新大赛重磅开启!

当国产算力崛起成为 AI 发展新引擎&#xff0c;你是否渴望用创新方案解锁无限可能&#xff1f;魔乐社区国产算力应用创新大赛重磅来袭&#xff01;聚焦国产算力前沿&#xff0c;无论你是开发者、研究者&#xff0c;还是技术爱好者&#xff0c;都能在这里一展身手。 现在报名参…

WebView 性能调试与优化全流程:加载速度与渲染性能双提升

移动端 WebView 页面通常用于承载复杂的前端应用&#xff0c;尤其是动态加载大量数据或进行高频率交互时&#xff0c;性能问题尤为突出。用户常常会遇到页面加载缓慢、滚动卡顿、甚至是部分内容显示不完全的情况。在这种情况下&#xff0c;如何优化数据加载与渲染过程&#xff…

51c嵌入式~CAN~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/14016935 一、CAN总线常见信号干扰问题 定位干扰原因 当总线有干扰时&#xff0c;有经验的工程师能够迅速定位&#xff0c;但是对于新手来说却很麻烦。 造成总线干扰的原因有很多&#xff0c;比如通过电磁辐射耦合到通…

【cursor实战】分析python下并行、串行计算性能

提示语 写一个Python并行计算、串行计算性能对比的代码。并行计算要包括多线程和多进程两种,计算的内容要比较复杂 模型 claude-4-sonnet 生成的代码 #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Python并行计算与串行计算性能对比程序 包含串行…

ubuntu中53端口被占用导致dnsmasq无法使用。已解决。

方案一&#xff1a;修改参数&#xff0c;但不影响使用 编辑配置文件 vim /etc/systemd/resolved.conf将此参数修改为&#xff1a; DNSStubListenerno重启服务 sudo systemctl daemon-reload sudo systemctl disable systemd-resolved.service方案一&#xff1a;直接禁用 编…

【多模态大模型】训练与推理直观解读

1.直观案例解读-图文问答 假设我们的输入是一张包含小猫的图片&#xff0c;以及一个文本提问&#xff1a;“其中是否有小猫&#xff1f;”。下面我将以最详尽的方式&#xff0c;描述数据在nanoVLM模型中从输入到输出的完整流动过程&#xff0c;并解释每一步中数据的形状和含义…

uni-app项目实战笔记17--获取系统信息getSystemInfo状态栏和胶囊按钮

接着上一篇笔记&#xff0c;在添加头部导航栏后&#xff0c;H5显示正常&#xff1a; 但在微信小程序中&#xff0c;由于刘海屏的存在&#xff0c;添加的头部导航栏跟状态栏重叠在一起&#xff1a; 因此需要获取状态栏的高度以便状态栏和导航栏错开不重叠在一起。同时头部导航栏…

Windows下Zookeeper客户端启动缓慢问题分析与解决方案

文章目录 1. 问题描述2. 问题分析2.1 性能分析2.2 根本原因 3. 解决方案3.1 临时解决方案3.2 长期解决方案 4. 注意事项5. 结论 1. 问题描述 在Windows 8.1 64-bit操作系统环境下&#xff0c;使用Curator框架连接Zookeeper时出现客户端启动异常缓慢的问题。具体表现为&#xf…

在 Java 中生成 PDF 缩略图(教程)

Java 本身无法自动生成 PDF 页面缩略图&#xff0c;但幸运的是&#xff0c;有许多软件库可以实现这一功能。本文示例使用我们自家的 JPedal 库&#xff0c;仅需几行 Java 代码即可创建缩略图。JPedal 是开发者使用的最佳 Java PDF 库。 如何使用 JPedal 将 PDF 转换为缩略图 …

基于大模型的甲状腺结节预测及综合诊疗技术方案大纲

目录 一、技术方案概述二、术前预测与方案制定2.1 结节特征分析与良恶性预测2.2 手术方案建议2.3 麻醉方案优化三、术中辅助决策3.1 实时数据监测与分析3.2 麻醉深度监控与调节四、术后护理与并发症预测4.1 术后恢复预测4.2 并发症风险预警五、统计分析与技术验证5.1 数据分割与…

SpringCloud系列(36)--SpringCloud Gateway简介

1、SpringCloud GateWay概述 SpringCloud Gateway是 Spring Cloud的一个全新项目&#xff0c;基于Spring 5.0Spring Boot 2.0和Project Reactor等技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统—的API路由管理方式&#xff1b;SpringCloud Gateway作为Sp…

TensorFlow深度学习实战:构建神经网络全指南

引言&#xff1a;深度学习与TensorFlow概览 深度学习作为机器学习的一个重要分支&#xff0c;近年来在计算机视觉、自然语言处理、语音识别等领域取得了突破性进展。TensorFlow是由Google Brain团队开发的开源深度学习框架&#xff0c;自2015年发布以来&#xff0c;已成为最受…

K8S: etcdserver: too many requests

Kubernetes etcdserver: too many requests 错误解决方案 当Kubernetes集群出现 etcdserver: too many requests 错误时&#xff0c;表明etcd数据库接收到的请求量超过了其处理能力。etcd作为Kubernetes的核心组件&#xff0c;存储着集群的所有状态数据&#xff0c;处理请求过…

银河麒麟高级服务器操作系统(全架构)OpenGauss 数据库部署手册

一、部署前准备工作 1. 环境检查 项目配置描述内存功能调试建议 32GB 以上。性能测试和商业部署时&#xff0c;单实例部署建议 128GB 以上。复杂的查询对内存的需求量比较高&#xff0c;在高并发场景下&#xff0c;可能出现内存不足。此时建议使用大内存的机器&#xff0c;或…