解释watch和computed的原理

131 阅读3分钟

1️⃣ 核心区别表

特性computedwatch
用途计算衍生数据监听变化执行副作用
返回值会返回值并缓存无返回值(主要触发回调)
执行时机访问 .value 时才执行数据变化时立即触发(flush 可控制)
缓存机制✅ 会缓存,依赖未变不重新计算❌ 不缓存,每次依赖变化都会触发回调
依赖收集自动收集依赖需手动指定监听对象(或 watchEffect 自动收集)
可获取 oldValue
异步支持❌ 仅同步计算✅ 可做异步副作用(API、DOM 操作等)
适合场景纯逻辑计算、UI 展示数据副作用逻辑、异步请求、操作 DOM 等

2️⃣ 理解方式

  1. computed = 计算属性(惰性求值 + 缓存)

    • 主要用于根据已有数据计算出新的数据
    • 访问 value 时才触发计算
    • 不适合做副作用
  2. watch = 数据侦听器(观察者模式)

    • 用于监听某个响应式变量变化
    • 触发回调执行副作用(API 请求、DOM 操作、日志等)
    • 可访问新旧值
    • 可以做异步操作

3️⃣ 原理上的差异

  • computed

    • 内部有 effect + dirty flag
    • 依赖变化时标记 dirty = true
    • 下一次访问 value 时重新计算
    • 可以缓存,减少重复计算
  • watch

    • 直接对响应式数据注册 effect
    • 数据变化时立即触发回调
    • 回调不返回值
    • 可选择 flush:pre/post/sync 控制执行时机

computed 的原理

computed 本质上是一个 带缓存的响应式副作用

  1. 依赖收集

    • 当访问 computed.value 时,Vue 会触发其内部 effect,追踪它依赖的响应式数据(ref 或 reactive 对象)。
  2. 惰性求值(lazy)

    • 初次访问时执行 getter,生成值并缓存
    • 标记 dirty = false
  3. 缓存机制

    • 当依赖变化时,响应式系统会将 dirty = true
    • 下次访问 .value 时重新计算并更新缓存
  4. 不可获取旧值

    • 因为 computed 只关注最新值,内部只存当前缓存

示意流程:

访问 computed.value → 检查 dirty
    ├─ dirty = true → 执行 getter → 缓存值 → dirty = false
    └─ dirty = false → 直接返回缓存值
依赖变化 → effect 标记 dirty = true

示例代码:

import { ref, computed } from 'vue'

const count = ref(1)
const double = computed(() => count.value * 2)

console.log(double.value) // 2
count.value++
console.log(double.value) // 4,重新计算

2️⃣ watch 的原理

watch 本质是一个 响应式数据的观察者(Watcher) ,用于执行副作用:

  1. 手动依赖选择

    • 你传入要监听的响应式对象(ref / reactive / getter)
    • Vue 内部会对其注册 effect
  2. 响应式触发

    • 当被监听的数据发生变化时,effect 会触发回调函数
  3. 可获取 oldValue / newValue

    • watch 在触发时会提供新值和旧值
  4. 异步可选

    • 默认 flush: 'pre'
    • 可以指定 'post'(DOM 更新后)或 'sync'(同步执行)
  5. 没有缓存

    • 每次数据变化都会调用回调函数
    • 适合副作用逻辑,不用于纯计算

示意流程:

watch(reactiveData, callback)
    └─ 内部创建 effect
依赖变化 → 触发 effect → callback(newValue, oldValue)

示例代码:

import { ref, watch } from 'vue'

const count = ref(1)

watch(count, (newVal, oldVal) => {
  console.log(`count changed from ${oldVal} to ${newVal}`)
})

count.value++  // 触发 watch 回调

3️⃣ 原理总结对比

特性computedwatch
内部机制effect + dirty flageffect 直接触发回调
触发时机访问 .value 时(懒执行)数据变化时立即触发
缓存✅ 缓存上次计算值❌ 不缓存,每次触发回调
oldValue❌ 无✅ 有
使用目的衍生数据计算执行副作用(异步、DOM 操作等)