Vue 监听器 watch 深度解析

前言

小编最近在复习vue,复习到vue的监听器,总结了以下几点:

完整代码示例

javascript 复制代码
watch(
  user, // 监听源
  (newVal) => { // 回调函数
    console.log('用户信息变化:', newVal)
  },
  { deep: true } // 配置选项
)

一、核心概念拆解

1. 三大核心要素

要素 说明 类比生活场景
监听源 被监视的数据源(用户信息对象) 监控摄像头对准的保险柜
回调函数 数据变化时触发的操作函数 保险柜被打开时触发的警报
配置选项 控制监听行为的参数设置 监控摄像头的灵敏度设置

2. 参数详解

javascript 复制代码
watch(source, callback, options)

二、逐层深入解析

1. 监听源(Source)

常见类型

  • 响应式对象reactive 创建)

  • ref 对象(含对象类型的 ref)

  • getter 函数() => obj.prop

js 复制代码
//来自官方文档
const x = ref(0)
const y = ref(0)

// single ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// array of multiple sources
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

可能这里有人对getter函数不太理解,下面是我找的资料:

一、本质理解

getter(获取器)这个名字来源于它的核心功能:获取并返回一个值。就像去自动售货机买东西:

  1. 普通函数:你按按钮 → 机器给你饮料(直接获取)
  2. getter函数:你按特定组合键 → 机器计算总价后给你结果(需要计算过程)

二、代码对照表

代码部分 现实类比 为什么叫 getter
() => x.value + y.value 收银员计算商品总价 需要"获取"计算结果
watch(..., (sum) => {}) 收银员发现总价变化时通知你 监控获取器返回值的变动

三、深入解析

1. 基本特征

  • 输入:依赖其他值(x/y)
  • 输出:计算后的新值(sum)
  • 特性:每次访问都会重新计算(除非缓存)

2. 与普通函数的区别

javascript 复制代码
// 普通函数
function getSum() {
  return x + y // 直接返回
}

// getter函数(在watch中)
() => x.value + y.value // 被监控的依赖关系,记住返回的是一个函数。若
const x = ref(1);
const y = ref(2);
const xy = () => x.value + y.value;
console.log(xy);//() => x.value + y.value
console.log(xy());// 3
需要调用才有值,或者使用computed属性

3. Vue 的特别处理

当把这个函数传给 watch 时:

  1. 依赖追踪:Vue 自动记录函数内部访问的响应式变量(x.value/y.value)
  2. 重新计算:当任意依赖变化时,自动重新执行这个函数
  3. 触发回调:如果返回的新值 ≠ 旧值,就执行回调函数

四、为什么必须用这种写法?

1. 错误写法示例

js 复制代码
// 错误!无法追踪依赖
watch(x.value + y.value, (sum) => {})

2. 正确写法原理

通过函数包装:

js 复制代码
// ✅ 正确!建立响应式依赖链
watch(
  () => x.value + y.value, // 依赖收集器
  (sum) => { /* 响应变化 */ }
)

五、类比其他场景

1. 计算属性中的 getter

js 复制代码
const total = computed({
  get: () => x.value + y.value, // 同款getter
  set: (val) => { /*...*/ }
})

2. 对象属性的 getter

js 复制代码
const obj = {
  get sum() { // 原生JS的getter
    return x + y
  }
}

特殊特性

  • 当监听整个对象时,默认不触发嵌套属性的变化
  • 需要配合 deep: true 才能深度监听

2. 回调函数(Callback)

参数解析

javascript 复制代码
(newValue, oldValue) => { /*...*/ }
  • 第一个参数:变化后的新值
  • 第二个参数:变化前的旧值
  • 注意 :对于对象类型,oldValue 会与 newValue 指向同一引用

3. 配置选项(Options)

选项 类型 默认值 作用说明
deep boolean false 深度监听嵌套属性变化
immediate boolean false 立即触发回调(初始值时)
flush string 'pre' 控制回调触发时机(pre/post/sync)

三、深度监听原理

1. 工作机制图解

graph TD A[user 对象] --> B[属性变更] B --> C{deep: true?} C -->|是| D[遍历所有子属性] C -->|否| E[仅监听顶层属性] D --> F[触发回调] E --> G[不触发回调]

2. 典型使用场景

  • 用户资料表单(嵌套多个字段)
  • 购物车商品对象(多层数据结构)
  • 复杂配置项对象

四、对比其他监听方式

1. watch vs watchEffect

特性 watch watchEffect
依赖收集方式 显式指定监听源 自动收集回调中的依赖
初始执行 需配置 immediate: true 立即执行
适用场景 精确控制监听目标 副作用操作(如日志记录)

2. 与 Vue 2 的对比

javascript 复制代码
// Vue 2 选项式 API
watch: {
  user: {
    handler(newVal) { /*...*/ },
    deep: true
  }
}

// Vue 3 组合式 API(更灵活)
const user = reactive({/*...*/})
watch(user, (newVal) => {/*...*/}, { deep: true })

五、最佳实践指南

1. 性能优化建议

  • 避免过度深度监听 :只对必要对象开启 deep

  • 使用精确监听路径

    javascript 复制代码
    // 优于深度监听
    watch(() => user.address.city, (newCity) => {...})
  • 及时清理监听

    javascript 复制代码
    const stopWatch = watch(...)
    onUnmounted(stopWatch) // 组件卸载时停止监听

2. 常见错误示例

javascript 复制代码
// 错误1:直接修改监听源导致无限循环
watch(user, (newVal) => {
  user.name = '新名字' // 会再次触发监听!
})

// 错误2:忘记处理异步操作
watch(user, async (newVal) => {
  const data = await fetchData() // 需要处理可能的竞态条件
})

// 正确解决方案:使用 watchEffect + 清理函数
watchEffect(async (onCleanup) => {
  let isValid = true
  onCleanup(() => { isValid = false })
  
  const data = await fetchData()
  if (isValid) { /* 处理数据 */ }
})

六、完整示例场景

用户资料编辑监控

javascript 复制代码
const user = reactive({
  id: 1,
  info: {
    name: '张三',
    address: {
      city: '北京',
      street: '朝阳区'
    }
  }
})

// 深度监听整个用户对象
watch(
  user,
  (newUser) => {
    console.log('用户信息已修改,自动保存...')
    autoSave(newUser)
  },
  { deep: true, immediate: true }
)

// 精确监听城市变化
watch(
  () => user.info.address.city,
  (newCity) => {
    updateMap(newCity)
  }
)

关键总结

  1. 使用 deep: true 时要考虑性能影响
  2. 优先使用精确监听路径替代深度监听
  3. 注意对象引用的特性,必要时使用深拷贝
  4. 异步操作要配合清理函数防止内存泄漏

官方文档参考:Vue 3 watch

相关推荐
qq. 28040339843 小时前
CSS层叠顺序
前端·css
喝拿铁写前端4 小时前
SmartField AI:让每个字段都找到归属!
前端·算法
猫猫不是喵喵.4 小时前
vue 路由
前端·javascript·vue.js
烛阴4 小时前
JavaScript Import/Export:告别混乱,拥抱模块化!
前端·javascript
bin91535 小时前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例12,TableView16_12 拖拽动画示例
前端·javascript·vue.js·ecmascript·deepseek
GISer_Jing5 小时前
[Html]overflow: auto 失效原因,flex 1却未设置min-height &overflow的几个属性以及应用场景
前端·html
程序员黄同学5 小时前
解释 Webpack 中的模块打包机制,如何配置 Webpack 进行项目构建?
前端·webpack·node.js
拉不动的猪5 小时前
vue自定义“权限控制”指令
前端·javascript·vue.js
再学一点就睡5 小时前
浏览器页面渲染机制深度解析:从构建 DOM 到 transform 高效渲染的底层逻辑
前端·css
拉不动的猪5 小时前
刷刷题48 (setState常规问答)
前端·react.js·面试