目录

一、watch 的工作原理

1. 基本概念

2. 工作流程

3. 核心原理图解

4. 源码简化版

二、watchEffect 的工作原理

1. 基本概念

2. 工作流程

3. 核心原理图解

4. 源码简化版

三、具体区别

1. 对比表格

2. 代码对比

3. 执行时机对比

4. 使用场景建议

四、面试怎么回答才精彩

🌟 精彩回答模板

🎯 面试加分话术

📝 一句话总结


一、watch 的工作原理

1. 基本概念

watch 是一个显式的侦听器,需要明确指定要监听的数据源。

2. 工作流程

import { ref, reactive, watch } from 'vue'

const count = ref(0)
const state = reactive({ name: 'Tom', age: 18 })

// 1. 监听单个 ref
watch(count, (newVal, oldVal) => {
  console.log(`count从 ${oldVal} 变为 ${newVal}`)
})

// 2. 监听 reactive 对象的某个属性(需要用 getter 函数)
watch(
  () => state.name,
  (newVal, oldVal) => {
    console.log(`name从 ${oldVal} 变为 ${newVal}`)
  }
)

// 3. 监听多个数据源
watch(
  [count, () => state.name],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log('多个值发生变化')
  }
)

// 4. 深度监听
watch(
  () => state,
  (newVal) => {
    console.log('state 深层变化')
  },
  { deep: true }
)

3. 核心原理图解

  1. 显式依赖: 你必须明确告诉 Vue 你要观察哪一个变量(Ref、Reactive、Getter 函数或其数组)。
  2. 懒执行(默认): 默认情况下,watch 是惰性的。只有当被监听的源发生变化时,才会执行回调。
  3. 获取前后值: 回调函数会收到两个参数:newValue(新值)和 oldValue(旧值)。
  4. 内部实现: Vue 内部会将监听源封装成一个 Effect。当监听源发生变化时,调度器会触发回调函数的执行。
┌─────────────────────────────────────────────────────┐
│                    watch 原理                        │
├─────────────────────────────────────────────────────┤
│                                                     │
│   指定数据源 ──────► 依赖收集 ──────► 变化时触发回调  │
│   (source)         (track)         (trigger)       │
│                                                     │
│   特点:                                            │
│   • 惰性执行(默认不立即执行)                        │
│   • 可以获取新旧值                                   │
│   • 明确知道是哪个依赖变化                           │
│                                                     │
└─────────────────────────────────────────────────────┘

4. 源码简化版

function watch(source, cb, options = {}) {
  let getter
  
  // 1. 标准化 source 为 getter 函数
  if (isRef(source)) {
    getter = () => source.value
  } else if (isReactive(source)) {
    getter = () => source
    options.deep = true // reactive 对象默认深度监听
  } else if (isFunction(source)) {
    getter = source
  }
  
  let oldValue
  
  // 2. 创建调度任务
  const job = () => {
    const newValue = effect.run()
    cb(newValue, oldValue)
    oldValue = newValue
  }
  
  // 3. 创建响应式 effect
  const effect = new ReactiveEffect(getter, job)
  
  // 4. 根据配置决定是否立即执行
  if (options.immediate) {
    job()
  } else {
    oldValue = effect.run()
  }
}

二、watchEffect 的工作原理

1. 基本概念

watchEffect 是一个隐式的侦听器,会自动追踪回调函数中使用的所有响应式依赖。

2. 工作流程

import { ref, reactive, watchEffect } from 'vue'

const count = ref(0)
const state = reactive({ name: 'Tom' })

// 1. 基本用法 - 自动追踪依赖
watchEffect(() => {
  // 自动追踪 count.value 和 state.name
  console.log(`count: ${count.value}, name: ${state.name}`)
})

// 2. 清除副作用
watchEffect((onCleanup) => {
  const timer = setInterval(() => {
    console.log(count.value)
  }, 1000)
  
  // 在下次执行前或组件卸载时调用
  onCleanup(() => {
    clearInterval(timer)
  })
})

// 3. 停止监听
const stop = watchEffect(() => {
  console.log(count.value)
})

// 手动停止
stop()

3. 核心原理图解

  1. 自动依赖收集: 不需要显式指定监听谁。它会立即执行一次,并在此过程中记录下函数内访问过的所有响应式属性。
  2. 立即执行: 它在组件初始化时会立即运行一次(相当于 watch 设置了 immediate: true)。
  3. 不追踪旧值: 它只关心当前的逻辑,不会记录或提供数据变化前的值。
  4. 内部实现: 它将传入的整个函数视为一个 Effect。执行时,任何触发的 getter 都会将其添加为依赖,只要依赖变了,整个函数重新运行。
┌─────────────────────────────────────────────────────┐
│                watchEffect 原理                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│   执行回调 ──────► 自动收集依赖 ──────► 依赖变化重新执行│
│   (run)           (track)           (trigger)      │
│                                                     │
│   特点:                                            │
│   • 立即执行(非惰性)                               │
│   • 无法获取旧值                                    │
│   • 自动追踪所有依赖                                 │
│                                                     │
└─────────────────────────────────────────────────────┘

4. 源码简化版

function watchEffect(effect, options = {}) {
  // 本质上是 watch 的简化版
  return doWatch(effect, null, options)
}

function doWatch(source, cb, options) {
  const getter = isFunction(source) ? source : () => traverse(source)
  
  let cleanup
  const onCleanup = (fn) => {
    cleanup = fn
  }
  
  const job = () => {
    // 执行清理函数
    if (cleanup) {
      cleanup()
    }
    // 执行 effect,传入 onCleanup
    effect.run()
  }
  
  const effect = new ReactiveEffect(
    () => getter(onCleanup),
    job
  )
  
  // watchEffect 默认立即执行
  effect.run()
}

三、具体区别

1. 对比表格

特性 watch watchEffect
依赖追踪 显式指定 自动追踪
立即执行 默认否(可配置 immediate) 默认是
获取旧值 ✅ 可以 ❌ 不可以
深度监听 需配置 deep: true 自动(访问到就追踪)
精确控制 更精确 较宽松
适用场景 特定数据变化执行逻辑 副作用同步

2. 代码对比

const userId = ref(1)
const userData = ref(null)

// ============ watch 写法 ============
watch(userId, async (newId, oldId) => {
  console.log(`用户ID从 ${oldId} 变为 ${newId}`)
  userData.value = await fetchUser(newId)
}, { immediate: true })

// ============ watchEffect 写法 ============
watchEffect(async () => {
  // 自动追踪 userId.value
  userData.value = await fetchUser(userId.value)
})

3. 执行时机对比

const count = ref(0)

// watch - 默认不立即执行
watch(count, (val) => {
  console.log('watch:', val)  // 初始不打印
})

// watchEffect - 立即执行
watchEffect(() => {
  console.log('watchEffect:', count.value)  // 立即打印: 0
})

count.value++ 
// 输出:
// watchEffect: 0  (立即执行)
// watch: 1
// watchEffect: 1

4. 使用场景建议

// ✅ 使用 watch 的场景
// 1. 需要获取变化前后的值
watch(searchText, (newVal, oldVal) => {
  analytics.track('search_changed', { from: oldVal, to: newVal })
})

// 2. 需要条件性地执行副作用
watch(status, (newStatus) => {
  if (newStatus === 'error') {
    showErrorModal()
  }
})

// 3. 监听特定属性的变化
watch(() => props.id, fetchData)

// ✅ 使用 watchEffect 的场景
// 1. 多个依赖的副作用同步
watchEffect(() => {
  document.title = `${user.name} - ${page.title}`
})

// 2. 需要立即执行的副作用
watchEffect(() => {
  localStorage.setItem('theme', theme.value)
})

// 3. 依赖关系复杂或动态变化
watchEffect(() => {
  if (showA.value) {
    console.log(dataA.value)
  } else {
    console.log(dataB.value)
  }
})

四、面试怎么回答才精彩

🌟 精彩回答模板

面试官:请说说 watch 和 watchEffect 的区别?


第一层:核心区别(30秒)

"watch 和 watchEffect 最核心的区别在于依赖追踪方式

  • watch 是显式追踪,需要明确指定监听的数据源
  • watchEffect 是隐式追踪,自动收集回调中使用的所有响应式依赖"

第二层:具体差异(1分钟)

"具体来说有四点不同:

  1. 执行时机:watch 默认惰性执行,watchEffect 立即执行
  2. 新旧值:watch 可以获取变化前后的值,watchEffect 不行
  3. 精确度:watch 更精确,watchEffect 更便捷
  4. 使用场景:watch 适合监听特定数据做逻辑处理,watchEffect 适合副作用同步"

第三层:原理层面(加分项)

"从原理上看,它们底层都是基于 ReactiveEffect 实现的:

// 简化理解
watchEffect(fn) ≈ watch(fn, null, { immediate: true })

watch 会将数据源标准化为 getter 函数,在 getter 执行时收集依赖;而 watchEffect 直接执行回调函数,在执行过程中自动追踪所有访问到的响应式数据。"


第四层:实战经验(亮点)

"在实际项目中,我的选择原则是:

  1. 需要对比新旧值 → 用 watch
  2. 需要条件判断是否执行 → 用 watch
  3. 多依赖的副作用同步 → 用 watchEffect
  4. 依赖关系动态变化 → 用 watchEffect

比如做搜索功能时,我会用 watch 监听搜索词,因为可能需要防抖和对比变化;而做主题切换同步到 localStorage,我会用 watchEffect,因为更简洁。"


🎯 面试加分话术

"其实 Vue3 还提供了 watchPostEffect 和 watchSyncEffect,
它们是 watchEffect 的别名,区别在于 flush 时机:

- watchEffect = watchEffect + { flush: 'pre' }
- watchPostEffect = watchEffect + { flush: 'post' }  // DOM 更新后执行
- watchSyncEffect = watchEffect + { flush: 'sync' }  // 同步执行

这在需要访问更新后的 DOM 时很有用。"

📝 一句话总结

watch 是"我告诉你监听谁",watchEffect 是"你自己看着办"。前者精确可控,后者简洁自动。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐