【DeepSeek帮我准备前端面试100问】(六)vue 的 watch 详解

135 阅读4分钟

Vue 的 watch 详解

watch 是 Vue 中用于观察和响应数据变化的重要特性。下面我将从多个方面全面详细地讲解 Vue 中的 watch

1. watch 的基本概念

1.1 什么是 watch

watch 是 Vue 提供的一种更通用的方式来观察和响应 Vue 实例上的数据变动。当需要在数据变化时执行异步或开销较大的操作时,watch 是最有用的。

1.2 基本用法

// 选项式 API
export default {
  data() {
    return {
      count: 0
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`)
    }
  }
}

// 组合式 API
import { ref, watch } from 'vue'

const count = ref(0)
watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})

2. watch 的多种使用方式

2.1 观察简单数据

watch: {
  username(newVal, oldVal) {
    // 当 username 变化时执行
  }
}

2.2 观察嵌套属性

watch: {
  'user.name'(newVal) {
    // 观察嵌套属性
  }
}

2.3 观察对象

watch: {
  user: {
    handler(newVal, oldVal) {
      // 注意:对于对象和数组,newVal 和 oldVal 会是同一个引用
    },
    deep: true // 深度观察
  }
}

2.4 立即触发

watch: {
  value: {
    handler(newVal) {
      // 立即执行一次
    },
    immediate: true
  }
}

3. watch 的工作原理

3.1 响应式系统集成

  • Vue 的响应式系统会追踪依赖
  • 当被观察的属性变化时,所有依赖它的 watcher 会被通知
  • watcher 会执行回调函数

3.2 与 computed 的区别

特性watchcomputed
用途观察数据变化执行副作用基于依赖计算新值
缓存有(依赖不变不重新计算)
异步操作支持不支持
返回值必须返回一个值

4. watch 的高级用法

4.1 观察多个源(Vue 3)

// 组合式 API
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

4.2 停止观察

// 组合式 API
const stop = watch(source, callback)

// 当不再需要时
stop()

4.3 观察响应式对象的部分属性(Vue 3)

watch(
  () => state.someObject.someProperty,
  (newVal, oldVal) => {
    // 仅观察特定属性
  }
)

4.4 控制触发时机

watch: {
  searchQuery: {
    handler(newVal) {
      // 执行操作
    },
    flush: 'post', // 在组件更新后触发
    debounce: 500  // 防抖 500ms
  }
}

5. watch 的性能考虑

5.1 避免过度使用 deep

  • 深度观察会遍历对象的所有属性,性能开销大
  • 尽量指定具体路径而非使用 deep

5.2 合理使用 immediate

  • 只在确实需要初始值时使用
  • 避免不必要的初始调用

5.3 大型数据结构的优化

  • 对于大型对象或数组,考虑观察特定属性而非整个对象
  • 或使用计算属性作为中间层

6. watch 的常见使用场景

6.1 表单验证

watch: {
  username(newVal) {
    this.validateUsername(newVal)
  }
}

6.2 路由变化响应

watch: {
  '$route'(to, from) {
    // 响应路由变化
  }
}

6.3 数据过滤

watch: {
  searchText: {
    handler() {
      this.filteredItems = this.items.filter(item => 
        item.text.includes(this.searchText)
    },
    immediate: true
  }
}

6.4 异步操作

watch: {
  id: {
    handler(newVal) {
      this.fetchData(newVal)
    },
    immediate: true
  }
}

7. Vue 2 和 Vue 3 的 watch 区别

特性Vue 2Vue 3
多个源观察不支持支持数组形式
停止观察this.$unwatch返回停止函数
观察方式字符串路径或函数主要使用 getter 函数
性能优化较少更高效的响应式系统

8. watch 的源码解析(简化)

// 简化的 watcher 实现
class Watcher {
  constructor(vm, expOrFn, cb, options) {
    this.vm = vm
    this.cb = cb
    this.deps = []
    
    // 解析表达式或获取函数
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    
    this.value = this.get()
  }
  
  get() {
    pushTarget(this) // 设置当前 watcher
    const value = this.getter.call(this.vm, this.vm)
    popTarget() // 恢复之前的 watcher
    return value
  }
  
  update() {
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}

9. watch 的最佳实践

  1. 明确观察目标:尽量观察具体的属性而非整个对象
  2. 合理使用 deep:只在必要时使用深度观察
  3. 考虑防抖:对于频繁变化的数据使用防抖
  4. 清理副作用:在组件销毁前清理异步操作
  5. 组合式思考:在 Vue 3 中考虑将相关逻辑组合在一起

10. 常见问题解答

Q1: watch 和 computed 该如何选择?

  • 使用 computed 当需要基于其他数据计算派生值
  • 使用 watch 当需要在数据变化时执行副作用(如异步操作、复杂逻辑)

Q2: 为什么 watch 对象时 newVal 和 oldVal 相同?

  • 这是 JavaScript 的对象引用特性,对于对象和数组,Vue 只存储引用
  • 如果需要比较旧值,可以深度克隆对象或使用特定属性观察

Q3: watch 能观察 Vuex 状态吗?

  • 可以,使用计算属性作为中间层:
    computed: {
      count() {
        return this.$store.state.count
      }
    },
    watch: {
      count(newVal) {
        // ...
      }
    }
    
  • Vue 3 中可以直接使用 watch(() => store.state.count, callback)

总结

Vue 的 watch 是一个强大的特性,它:

  1. 提供了灵活的数据观察方式
  2. 支持多种观察模式(深度、立即执行等)
  3. 适合处理异步操作和副作用
  4. 在 Vue 3 中得到了进一步增强

合理使用 watch 可以使你的 Vue 应用更加响应式和健壮,特别是在处理复杂状态逻辑和异步操作时。