Vue 监听器 watch 深度解析

公開日: 2025-03-27 23:27 1660文字 9 min read

做产品前,先别急着写代码:我是怎么判断一个点子值不值得做的想偷卷?但微信不支持md文档?这个软件助你!新手小白如何开发练手项目?前端常见安全问题 + 防御方法 + 面试回答吉比特(雷霆游戏)前端二面问题总结吉比特27前端实习面试纷享销客前端实习一面08-刚体约束 Constraint07-监听碰撞事件和获取碰撞信息06 - 物体休眠与休眠事件详解(Cannon.js)05-碰撞与碰撞组04 - 弹性与接触材质详解03 - Cannon 材质与摩擦系数设置02 - Cannon 引擎基础碰撞讲解01 - Cannon.js 引擎的用途和基本使用一款减轻前端图片命名工作量的图片转换器总结:一个双非大学生的两年,是怎么过来的?数学公式Input-输入框组件实现详解用HTML5实现实时ASCII艺术摄像头object-fit: contain - CSS 属性详解GSAP (GreenSock Animation Platform) 动画库学习指南Webview通信系统学习指南实现手势操控3D粒子动画?太好玩了!东田数码科技前端面试前端性能优化面试回答技巧(一)React与Vue表单的对比差异融山科技前端面经·React的入门学习(一)九方前端面试JavaScript 核心:数组/字符串 API 精要指南JavaScript的常用数组API原理解析前端练习小网站-新手突破之路前端の骚操作代码合集 (二)| 让你的网页变得有趣前端Threejs入门(五)Vue 3 响应式原理:computed的模板解包机制Vue 监听器 watch 深度解析Axios 深入解析从0到1配置vue项目跟git仓库连接!前端Three.js入门(四)你懂keep-alive吗?见过Vue的小区保安吗?前端Three.js入门(三)前端Three.js入门(二)前端Three.js入门(一)-Three的第一个简单的页面 Git 大冒险:解锁代码管理的秘密武器如何将网站加载速度提升50%?前端性能优化全攻略前端の骚操作代码合集 | 让你的网页充满恶趣味进击蓝桥杯!2025web前端HTML5uniapp-页面生命周期
この投稿は「日本語」では表示できません。元の投稿を表示しています。
Vue 监听器 watch 深度解析 作者: 前端AC | 原文: https://juejin.cn/post/7486359711845646374 前言 小编最近在复习vue,复习到vue的监听器,总结了以下几点: 完整代码示例 javascript watch user, // 监...

Vue 监听器 watch 深度解析

作者: 前端AC | 原文: https://juejin.cn/post/7486359711845646374

前言

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

完整代码示例

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

一、核心概念拆解

1. 三大核心要素

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

2. 参数详解

watch(source, callback, options)

二、逐层深入解析

1. 监听源(Source)

常见类型

  • 响应式对象reactive 创建)

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

  • getter 函数() => obj.prop

  • //来自官方文档
    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. 与普通函数的区别

    // 普通函数
    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. 错误写法示例

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

    2. 正确写法原理

    通过函数包装:

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

    五、类比其他场景

    1. 计算属性中的 getter

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

    2. 对象属性的 getter

    const obj = {
      get sum() { // 原生JS的getter
        return x + y
      }
    }

特殊特性

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

2. 回调函数(Callback)

参数解析

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

3. 配置选项(Options)

选项类型默认值作用说明
deepbooleanfalse深度监听嵌套属性变化
immediatebooleanfalse立即触发回调(初始值时)
flushstring’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

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

2. 与 Vue 2 的对比

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

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

五、最佳实践指南

1. 性能优化建议

  • 避免过度深度监听:只对必要对象开启 deep
  • 使用精确监听路径
    // 优于深度监听
    watch(() => user.address.city, (newCity) => {...})
  • 及时清理监听
    const stopWatch = watch(...)
    onUnmounted(stopWatch) // 组件卸载时停止监听

2. 常见错误示例

// 错误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) { /* 处理数据 */ }
})

六、完整示例场景

用户资料编辑监控

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