【从零开始学习Vue|第五篇】侦听器

4 阅读3分钟

1. 前置知识

  1. try,catch,finally
┌─────────────────────────────────────────┐
│  try {                                  │
│    // 尝试执行的代码                     │// 如果出错,跳到 catch               │
│  }                                      │
│  catch (error) {                        │
│    // 出错时执行的代码                   │// error 是错误信息                  │
│  }                                      │
│  finally {                              │
│    // 不管成功失败都执行                 │// 通常用于清理工作                   │
│  }                                      │
└─────────────────────────────────────────┘
  1. async/awaiit :异步语法
  • async关键字:async 放在函数前面,表示这个函数是异步的
// 普通函数
function sayHello() {
  return 'Hello'
}

// async 函数
async function sayHelloAsync() {
  return 'Hello'
}

sayHello()        // 返回:'Hello'
sayHelloAsync()   // 返回:Promise { 'Hello' }

async 函数 永远返回promise

  • await关键字:await 只能用在async函数内部,意思是等待
async function cookDinner() {
  console.log('1. 开始煮饭')
  const rice = await cookRice()    // ⏸️ 暂停,等饭煮好
  console.log('2. 饭好了')
  
  console.log('3. 开始炒菜')
  const dish = await cookDish()    // ⏸️ 暂停,等菜炒好
  console.log('4. 菜好了')
  
  return { rice, dish }
}

开始 → 煮饭 → ⏸️等待 → 饭好 → 炒菜 → ⏸️等待 → 菜好 → 结束

2. 侦听数据源类型

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

a. 侦听ref(包括计算属性)

import { ref, watch } from 'vue'

const count = ref(0)

// 直接传 ref,不需要 .value
watch(count, (newVal, oldVal) => {
  console.log('count 变了:', oldVal, '→', newVal)
})

count.value++  // 触发 watch

b. 侦听响应式对象(reactive)

import { reactive, watch } from 'vue'

const user = reactive({
  name: '张三',
  age: 25
})

// 直接传 reactive 对象
watch(user, (newUser, oldUser) => {
  console.log('user 对象变了:', newUser)
})

// 修改属性会触发
user.name = '李四'  // ✅ 触发 watch
user.age = 26       // ✅ 触发 watch

重要区别

// ref - 监视整个 ref
const count = ref(0)
watch(count, callback)  // count.value 变化时触发

// reactive - 监视对象内部属性
const obj = reactive({ a: 1 })
watch(obj, callback)    // obj.a 变化时触发

c. 侦听getter函数

  • getter函数就是返回响应式数据的函数
import { ref, watch } from 'vue'

const state = ref({ count: 0 })

// getter 函数:返回 state.value.count
watch(
  () => state.value.count,  // ← getter 函数
  (newVal, oldVal) => {
    console.log('count 变了:', oldVal, '→', newVal)
  }
)

state.value.count++  // 触发 watch

为什么要用getter喊输呢?

ⅰ. 场景1:侦听对象的某个属性

const user = reactive({ name: '张三', age: 25 })

// ❌ 错误:这样监视的是整个对象
watch(user, callback)

// ✅ 正确:只监视 name 属性
watch(
  () => user.name,  // getter 函数
  (newName, oldName) => {
    console.log('名字变了:', oldName, '→', newName)
  }
)

ⅱ. 场景2:侦听表达式

const price = ref(100)
const quantity = ref(2)

// 监视计算表达式
watch(
  () => price.value * quantity.value,  // getter 函数
  (newTotal, oldTotal) => {
    console.log('总价变了:', oldTotal, '→', newTotal)
  }
)

ⅲ. 场景3:深层嵌套属性

const state = reactive({
  user: {
    profile: {
      name: '张三'
    }
  }
})

// 只监视深层的 name
watch(
  () => state.user.profile.name,
  (newName, oldName) => {
    console.log('名字变了')
  }
)

d. 侦听多个数据源(数组)

import { ref, watch } from 'vue'

const name = ref('张三')
const age = ref(25)

// 数组形式监视多个数据源
watch(
  [name, age],  // ← 数组
  ([newName, newAge], [oldName, oldAge]) => {
    console.log('name:', oldName, '→', newName)
    console.log('age:', oldAge, '→', newAge)
  }
)

name.value = '李四'   // 触发
age.value = 26        // 触发

3. 深层侦听器

直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
})

obj.count++

如果给上面这个例子显式地加上 deep 选项,强制转成深层侦听器:

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)