普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月25日首页

学习笔记--vue3 watchEffect监听的各种姿势用法和总结

2026年3月25日 17:09

watchEffect 监听不同数据源

watchEffect 会自动追踪在其回调函数中使用的所有响应式依赖,无需显式指定数据源。

1. 监听单个 ref

import { ref, watchEffect } from 'vue'

const count = ref(0)

// 自动追踪 count
watchEffect(() => {
  console.log('count 值:', count.value)
  // 当 count 变化时自动执行
})

// 修改值会触发
count.value++ // 输出: count 值: 1

2. 监听多个 ref

import { ref, watchEffect } from 'vue'

const count = ref(0)
const name = ref('John')
const age = ref(18)

// 自动追踪所有使用的 ref
watchEffect(() => {
  console.log(`姓名: ${name.value}, 年龄: ${age.value}, 计数: ${count.value}`)
  // 当 name、age 或 count 任何一个变化时都会执行
})

// 任何修改都会触发
count.value++  // 触发
name.value = 'Jane'  // 触发
age.value = 20  // 触发

3. 监听单个 reactive

import { reactive, watchEffect } from 'vue'

const state = reactive({
  count: 0,
  name: 'John'
})

// 方式1:直接使用整个对象(会深度追踪所有属性)
watchEffect(() => {
  console.log('state 整体:', state)
  // 当 state 的任何属性变化时都会触发
})

// 方式2:只追踪特定属性(性能更好)
watchEffect(() => {
  console.log('count 值:', state.count)
  // 只有当 state.count 变化时才触发
})

// 修改会触发
state.count++  // 触发方式1和方式2
state.name = 'Jane'  // 只触发方式1

4. 监听多个 reactive

import { reactive, watchEffect } from 'vue'

const user = reactive({
  name: 'John',
  age: 18
})

const settings = reactive({
  theme: 'dark',
  language: 'zh'
})

// 自动追踪所有使用的 reactive 属性
watchEffect(() => {
  console.log(`用户: ${user.name}, ${user.age}岁`)
  console.log(`设置: ${settings.theme}主题, ${settings.language}语言`)
  // 当 user.name、user.age、settings.theme、settings.language 任一变化时触发
})

// 修改会触发
user.name = 'Jane'  // 触发
settings.theme = 'light'  // 触发

5. 混合监听 ref 和 reactive

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

const count = ref(0)
const user = reactive({
  name: 'John',
  info: {
    age: 18,
    city: 'Beijing'
  }
})

// 自动追踪所有使用的响应式数据
watchEffect(() => {
  console.log(`计数: ${count.value}`)
  console.log(`用户: ${user.name}`)
  console.log(`年龄: ${user.info.age}`)
  console.log(`城市: ${user.info.city}`)
  // 依赖:count.value、user.name、user.info.age、user.info.city
})

// 任何依赖变化都会触发
count.value++  // 触发
user.name = 'Jane'  // 触发
user.info.age = 20  // 触发
user.info.city = 'Shanghai'  // 触发

6. 监听嵌套 reactive 对象

import { reactive, watchEffect } from 'vue'

const state = reactive({
  user: {
    profile: {
      name: 'John',
      address: {
        city: 'Beijing',
        street: 'Main St'
      }
    }
  }
})

// 深度追踪:会自动追踪所有访问的嵌套属性
watchEffect(() => {
  console.log('城市:', state.user.profile.address.city)
  console.log('街道:', state.user.profile.address.street)
  // 只追踪 city 和 street 的变化
})

// 修改嵌套属性会触发
state.user.profile.address.city = 'Shanghai'  // 触发
state.user.profile.address.street = 'Nanjing Rd'  // 触发

// 修改未追踪的属性不会触发
state.user.profile.name = 'Jane'  // 不会触发(未在回调中使用)

7. watchEffect 的清理和停止

import { ref, watchEffect } from 'vue'

const count = ref(0)

// watchEffect 返回停止函数
const stop = watchEffect((onCleanup) => {
  console.log('count:', count.value)
  
  // 清理函数:在重新运行前或停止时执行
  onCleanup(() => {
    console.log('清理副作用')
    // 用于取消请求、清除定时器等
  })
})

// 停止监听
stop()

8. 异步 watchEffect

import { ref, watchEffect } from 'vue'

const id = ref(1)
const data = ref(null)

watchEffect(async (onCleanup) => {
  let cancelled = false
  
  onCleanup(() => {
    cancelled = true
  })
  
  // 模拟异步请求
  const response = await fetch(`/api/data/${id.value}`)
  if (!cancelled) {
    data.value = await response.json()
  }
})

9. 控制执行时机

import { ref, watchEffect } from 'vue'

const count = ref(0)

// flush: 'pre' (默认) - 组件更新前执行
watchEffect(() => {
  console.log('pre:', count.value)
}, {
  flush: 'pre'
})

// flush: 'post' - 组件更新后执行
watchEffect(() => {
  console.log('post:', count.value)
}, {
  flush: 'post'
})

// flush: 'sync' - 同步执行
watchEffect(() => {
  console.log('sync:', count.value)
}, {
  flush: 'sync'
})

10. watchEffect vs watch 对比

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

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

// watch: 显式指定数据源
watch(count, (newVal, oldVal) => {
  console.log('watch - count:', newVal, oldVal)
})

watch(
  [() => state.name, () => state.age],
  ([newName, newAge], [oldName, oldAge]) => {
    console.log('watch - name/age:', newName, newAge)
  }
)

// watchEffect: 自动追踪依赖
watchEffect(() => {
  console.log('watchEffect - count:', count.value)
  console.log('watchEffect - name/age:', state.name, state.age)
  // 自动追踪 count.value、state.name、state.age
})

// 执行时机
// watch: 懒执行,只有数据变化时才执行
// watchEffect: 立即执行一次,然后依赖变化时执行

实际应用示例

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

// 用户搜索示例
const searchKeyword = ref('')
const filters = reactive({
  category: 'all',
  sortBy: 'date',
  priceRange: [0, 1000]
})
const results = ref([])

// 自动搜索:任何搜索条件变化时自动执行
watchEffect(async () => {
  console.log('搜索条件变化,重新获取数据')
  
  // 构建查询参数
  const params = {
    keyword: searchKeyword.value,
    category: filters.category,
    sortBy: filters.sortBy,
    minPrice: filters.priceRange[0],
    maxPrice: filters.priceRange[1]
  }
  
  // 模拟 API 请求
  const response = await fetch(`/api/search?${new URLSearchParams(params)}`)
  results.value = await response.json()
})

// 任何条件变化都会触发搜索
searchKeyword.value = 'vue'  // 触发搜索
filters.category = 'books'   // 触发搜索
filters.sortBy = 'rating'    // 触发搜索
filters.priceRange = [0, 500] // 触发搜索

总结对比

特性 watch watchEffect
数据源 显式指定 自动追踪依赖
执行时机 懒执行(首次不执行) 立即执行
旧值获取 ✅ 可以获取 ❌ 无法获取
监听多个 需要数组 自动收集
嵌套对象 需要 deep 选项 自动深度追踪(访问到的属性)
性能优化 更精确控制 自动优化

最佳实践

  1. 使用 watchEffect 当

    • 不需要获取旧值
    • 依赖关系简单且自动
    • 需要立即执行副作用
  2. 使用 watch 当

    • 需要获取旧值
    • 需要精确控制监听的数据源
    • 需要懒执行(首次不执行)
  3. 性能优化

    // ❌ 避免:访问过多属性导致频繁执行
    watchEffect(() => {
      console.log(state)  // 任何属性变化都触发
    })
    
    // ✅ 推荐:只访问需要的属性
    watchEffect(() => {
      console.log(state.name, state.age)  // 只有这些属性变化才触发
    })
    

# 学习笔记--vue3 watch监听的各种姿势用法和总结

学习笔记--vue3 watch监听的各种姿势用法和总结

2026年3月25日 16:58

在 Vue 3 中,watch 监听不同数据源的方式有所不同

1. 监听单个 ref

import { ref, watch } from 'vue'

const count = ref(0)

// 直接传入 ref
watch(count, (newVal, oldVal) => {
  console.log('count 变化:', newVal, oldVal)
})

// 或者使用 getter 函数
watch(() => count.value, (newVal, oldVal) => {
  console.log('count 变化:', newVal, oldVal)
})

2. 监听多个 ref

import { ref, watch } from 'vue'

const count = ref(0)
const name = ref('John')

// 方式1:使用数组
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
  console.log('count 变化:', newCount, oldCount)
  console.log('name 变化:', newName, oldName)
})

// 方式2:使用 getter 数组
watch(
  [() => count.value, () => name.value],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log('多个数据变化')
  }
)

3. 监听单个 reactive

import { reactive, watch } from 'vue'

const state = reactive({
  count: 0,
  name: 'John'
})

// ❌ 错误:直接传入 reactive 对象无法监听到内部属性的变化
watch(state, (newVal, oldVal) => {
  console.log('不会触发') // 深度监听时才会触发
})

// ✅ 正确:使用 getter 函数监听特定属性
watch(
  () => state.count,
  (newVal, oldVal) => {
    console.log('count 变化:', newVal, oldVal)
  }
)

// ✅ 深度监听整个 reactive 对象
watch(
  () => state,
  (newVal, oldVal) => {
    console.log('state 任何属性变化都会触发')
  },
  { deep: true }
)

4. 监听多个 reactive 数据

import { reactive, watch } from 'vue'

const state1 = reactive({ count: 0 })
const state2 = reactive({ name: 'John' })

// 方式1:使用 getter 数组
watch(
  [() => state1.count, () => state2.name],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log('数据变化')
  }
)

// 方式2:深度监听整个 reactive 对象(不推荐)
watch(
  [() => state1, () => state2],
  ([newState1, newState2], [oldState1, oldState2]) => {
    // 注意:oldState1 和 newState1 指向同一个对象
    console.log('状态变化')
  },
  { deep: true }
)

5. 混合监听 ref 和 reactive

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

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

watch(
  [count, () => state.name, () => state.age],
  ([newCount, newName, newAge], [oldCount, oldName, oldAge]) => {
    console.log('混合数据变化')
  }
)

6. 监听响应式对象的属性

import { reactive, watch } from 'vue'

const user = reactive({
  info: {
    name: 'John',
    address: {
      city: 'Beijing'
    }
  }
})

// 监听嵌套属性
watch(
  () => user.info.address.city,
  (newVal, oldVal) => {
    console.log('城市变化:', newVal, oldVal)
  }
)

// 深度监听整个对象
watch(
  () => user,
  (newVal, oldVal) => {
    console.log('user 任何变化')
  },
  { deep: true }
)

总结对比

数据源 监听方式 注意事项
单个 ref watch(count, callback) 直接传入即可
多个 ref watch([ref1, ref2], callback) 使用数组形式
单个 reactive 属性 watch(() => state.prop, callback) 必须使用 getter
多个 reactive 属性 watch([() => state.prop1, () => state.prop2], callback) 使用 getter 数组
整个 reactive watch(() => state, callback, { deep: true }) 必须深度监听

最佳实践建议

  1. 优先使用 getter 函数,特别是监听 reactive 对象的属性
  2. 避免深度监听大型对象,可能会影响性能
  3. 注意旧值的引用问题:对于 reactive 对象,旧值可能与新值相同(因为引用未变)
❌
❌