watch

80 阅读5分钟

watch:

在 Vue.js 中,watch 是用于监听数据变化的核心 API,常用于执行副作用逻辑(如异步请求、复杂计算等)。


一、基本用法

1. 简单监听

data: {
        count: ''
      },
watch: {
  // 监听 data 中的属性
  count(newVal, oldVal) {
    console.log('count 变化:', newVal, oldVal);
  }
}  // oldValue 老值(一般不用)

2. 监听对象属性

data: {
        obj: {
            words: ''
          }
      },
watch: {
  // 监听对象深层属性(需用字符串路径)
  'obj.words'(newVal) {
    console.log('obj.words 变化:', newVal);
  }
}

二、配置选项(对象形式)

通过对象形式配置 handler 和选项:

data: {
        obj: {
          words: '小黑',
          lang: 'italy'
        },
        result: '', // 翻译结果
      },
watch: {
  obj: {
    handler(newVal, oldVal) {
      // 处理逻辑
    },
    deep: true,      // 深度监听
    immediate: true   // 立即执行,一进入页面handler就立刻执行一次
  }
}

1. deep: true(深度监听)

  • 作用:监听对象或数组内部嵌套值的变化。
  • 场景:监听复杂数据结构(如对象、数组)。
  • 示例
    watch: {
      obj: {
        handler(newVal) {
          console.log('user 内部变化:', newVal);
        },
        deep: true
      }
    }
    

2. immediate: true(立即执行)

  • 作用:在组件初始化时立即触发一次回调。
  • 场景:需要初始数据时(如页面加载时自动请求)。
    watch: {
      keyword: {
        handler(newVal) {
          this.search(newVal);
        },
        immediate: true  // 初始化时执行一次
      }
    }
    

三、监听多个数据(数组形式)

同时监听多个数据源,触发同一回调:

watch: {
  // 监听多个数据,回调参数为数组
  [data1, data2](newValues, oldValues) {
    const [newData1, newData2] = newValues;
    const [oldData1, oldData2] = oldValues;
    // 处理逻辑
  }
}

四、this.$watch(动态监听)

在组件实例中动态添加监听器:

export default {
  created() {
    // 动态监听 dataKey 的变化,并保存取消函数
    this.unwatch = this.$watch('dataKey', (newVal) => {
      // 处理逻辑
    }, { deep: true, immediate: true });
  },
  beforeDestroy() {
    // 手动取消监听
    this.unwatch();
  }
}

五、适用场景

1、异步操作(API 请求)

场景说明:当数据变化需要触发异步操作(如接口请求、定时任务)时,watch 是最佳选择。

典型场景

  • 搜索框输入联想(用户输入关键词后自动请求搜索结果)
  • 表单字段变化后实时校验(如验证用户名是否被占用)
  • 分页参数变化后重新加载数据

示例

watch: {
  searchKeyword(newVal) {
    // 防抖处理(避免频繁请求)
    clearTimeout(this.timer);
    this.timer = setTimeout(async () => {
      const res = await axios.get('/api/search', { params: { q: newVal } });
      this.results = res.data;
    }, 300);
  }
}

注意事项

  • 必须配合 防抖(Debounce)  或 节流(Throttle)  避免高频请求。
  • 需要处理异步错误(如添加 try/catch)。

2、复杂数据逻辑(依赖多个值变化)

场景说明:当需要根据多个数据的变化组合计算,或执行复杂逻辑时(如联动操作)。

典型场景

  • 表单多字段组合校验(如密码和确认密码是否一致)
  • 购物车商品数量和总价联动更新
  • 地图组件中经纬度变化后重新定位

示例

watch: {
  // 监听多个数据(数组形式)
  ['username', 'password']([newUsername, newPassword], [oldUsername, oldPassword]) {
    if (newUsername !== oldUsername) {
      this.checkUsernameAvailability(newUsername);
    }
    if (newPassword.length < 6) {
      this.showError('密码至少6位');
    }
  }
}

3、深度监听对象/数组(Deep Watch)

场景说明:当需要监听对象内部属性或数组元素变化时(如复杂表单对象、配置项)。

典型场景

  • 监听富文本编辑器的内容变化(如 editor.content
  • 监听表格行数据(数组元素)的修改
  • 监听嵌套配置对象(如主题色、用户偏好设置)

示例

watch: {
  userProfile: {
    handler(newVal) {
      // 当 userProfile 内部属性变化时触发
      this.saveToLocalStorage(newVal);
    },
    deep: true  // 深度监听
  }
}

注意事项

  • 避免滥用 deep: true,尤其是监听大型对象时可能引发性能问题。
  • 优先监听具体属性路径(如 'userProfile.name')减少开销。

4、动态路径监听(Dynamic Path)

场景说明: 当需要监听的属性路径是动态生成时(如根据用户选择动态监听不同字段)。

典型场景

  • 动态表单字段(如根据选择的问卷类型监听不同题目)
  • 路由参数变化监听(如 $route.params.id

示例

watch: {
  // 监听动态路由参数
  '$route.params.id'(newId) {
    this.loadData(newId);
  }
}

5、组件间通信(父子组件数据同步)

场景说明:当需要监听父组件传递的 prop 变化并同步到子组件内部状态时。

典型场景

  • 父组件传递 selectedTab,子组件监听并切换标签页
  • 父组件传递 initialValue,子组件监听并初始化表单

示例

export default {
  props: ['initialValue'],
  data() {
    return {
      internalValue: this.initialValue
    };
  },
  watch: {
    initialValue(newVal) {
      // 父组件传入的值变化时,同步到子组件内部状态
      this.internalValue = newVal;
    }
  }
};

6、手动控制响应式行为

场景说明:当需要对数据变化做精细控制(如跳过初始监听、延迟执行)。

典型场景

  • 仅在某些条件满足时触发监听(如用户主动提交后才监听表单)
  • 延迟执行副作用(如等待动画结束后再更新数据)

示例

watch: {
  dataKey: {
    handler(newVal) {
      if (this.isActive) {
        // 仅在 isActive 为 true 时执行
        this.doSomething(newVal);
      }
    },
    immediate: true  // 初始化时也触发一次
  }
}

7、与第三方库集成

场景说明:当需要将 Vue 数据与第三方库(如 D3.js、地图库、图表库)同步时。

典型场景

  • 数据变化后更新图表
  • 监听地图中心坐标变化后重新渲染

示例

watch: {
  chartData: {
    handler(newData) {
      // 销毁旧图表,重新渲染新数据
      this.d3Chart.destroy();
      this.d3Chart = new D3Chart(this.$el, newData);
    },
    deep: true
  }
}


六、与 computed 的区别

特性watchcomputed
用途监听数据变化,执行副作用(如请求、DOM 操作)基于依赖数据动态计算新值
缓存无缓存有缓存(依赖不变时直接返回缓存值)
同步/异步支持异步操作必须是同步操作
响应触发条件数据变化时触发依赖数据变化时触发

七、注意

1. 避免滥用 deep

  • 深度监听会遍历对象所有属性,可能影响性能。
  • 优先监听具体属性路径(如 'obj.prop')。

2. 防抖与节流

  • 防抖(Debounce):高频触发时延迟执行(如搜索框输入)。
    watch: {
      keyword: {
        handler: _.debounce(function(newVal) {
          this.search(newVal);
        }, 300),
        immediate: true
      }
    }
    
  • 节流(Throttle):限制触发频率(如滚动事件)。

3. 清理副作用

  • 在组件销毁前清除定时器、取消请求等:
    beforeDestroy() {
      clearTimeout(this.timer);
    }
    

4. 监听引用类型

  • 直接修改对象/数组的某个属性不会触发监听,需替换整个对象:
    // ❌ 不会触发监听
    this.obj.name = 'newName';
    
    // ✅ 触发监听
    this.obj = { ...this.obj, name: 'newName' };
    

5.何时避免使用 watch

  • 纯计算场景:优先使用 computed(如拼接字符串、过滤列表)。
  • 高频触发且无需实时性:改用事件驱动(如手动按钮提交代替自动保存)或 需结合防抖/节流。
  • 简单数据同步:使用 v-model 或 .sync 修饰符。

八、常见问题

1. 为什么 watch 不触发?

  • 数据未被 Vue 响应式系统追踪(如未在 data 中声明)。
  • 直接修改数组索引或对象属性(需使用 Vue.set)。

2. watch$watch 的区别?

  • watch 是组件选项,$watch 是实例方法(动态添加监听)。
特性watch选项$watch方法
定义位置在组件选项中静态声明在代码中动态调用(如 created 钩子)
配置方式通过对象配置 handlerdeep 等直接传递回调函数和配置对象
取消监听自动随组件销毁取消需手动调用返回的 unwatch 函数
灵活性适合固定监听逻辑适合动态条件触发的监听(如按需监听)
监听动态路径需预定义路径(如 'obj.prop'可通过变量动态生成路径