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

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}
})

相关文章:

  • deal 网站要怎么做杭州关键词优化服务
  • 美女做暖暖免费视频2017网站seo工作室
  • 网站制作周期网络营销总结及体会
  • 响应式网站开发方法十大免费网站推广
  • 青岛做企业网站的公司网络销售的工作内容
  • b2c平台网站建设专业做seo推广
  • Android14音频子系统-Audio HAL分析
  • H5录音、图文视频IndexDB储存最佳实践:用AI生成语音备忘录
  • 华为云Flexus+DeepSeek征文|基于Dify+ModelArts打造智能客服工单处理系统
  • 了解笔记本电脑制造:从品牌到代工厂的全产业链
  • Android14音频子系统-ASoC-ALSA之DAPM电源管理子系统
  • 鸿蒙与h5的交互
  • 基于Kafka实现企业级大数据迁移的完整指南
  • 2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷(一)
  • 跨线程connect传参的错误
  • Dify、n8n、Coze、FastGPT 和 Ragflow 对比分析:如何选择最适合你的智能体平台?
  • 一款实验室创客实验室用的桌面式五轴加工中心
  • 深入理解残差网络(ResNet):原理与PyTorch实现
  • github 上的php项目
  • java 导出word 实现循环表格
  • Ubuntu 物理桌面远程访问教程(基于 RealVNC / mstsc)
  • npm 报错:“无法加载文件 ...npm.ps1,因为在此系统上禁止运行脚本” 解决方案(附执行策略说明)
  • 暴雨信创电脑代理商成功中标长沙市中医康复医院
  • docker搭建mysql主从集群
  • 笔记01:现有PCB文件自动生成PCB库
  • 分布式系统 - 分布式缓存及方案实现