Vue3-watch在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

151 阅读3分钟

问题: 在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

在 Vue 3 中,当使用 watch 监听一个对象并启用 深层级模式(deep: true)时,如果对象的某个内部属性改变,回调函数的 新值 和 旧值 都会指向同一个对象引用!

这是因为 JavaScript 的对象是 引用类型,Vue 监听到变更后,虽然数据内容变了,但新旧值的引用仍然一致。让我用一个例子来演示!

🔧 示例:深层级监听对象变更

< script setup >
    import {
        reactive,
        watch
    } from 'vue'

const user = reactive({
    name: 'Alice',
    address: {
        city: 'New York',
        zip: '10001'
    }
})

// 监听整个对象,开启 deep 模式
watch(user, (newVal, oldVal) => {
    console.log('新值:', newVal)
    console.log('旧值:', oldVal)
    console.log('新值 === 旧值:', newVal === oldVal) // true
}, {
    deep: true
})

// 修改深层级属性
setTimeout(() => {
        user.address.city = 'Los Angeles'
    }, 1000) <
    /script>

🧠 输出结果

新值: {
    name: 'Alice',
    address: {
        city: 'Los Angeles',
        zip: '10001'
    }
}
旧值: {
    name: 'Alice',
    address: {
        city: 'Los Angeles',
        zip: '10001'
    }
}
新值 === 旧值: true

🔑 关键点解释

  1. 深层监听:deep: true 监听对象内部属性变化。
  2. 引用一致:reactive 返回的是同一个对象引用,newVal 和 oldVal 指向同一对象。
  3. 数据变更触发回调:即使引用相同,Vue 仍能侦测到深层变更,并触发回调。 🛠 解决方案(克隆对象对比) 如果你确实需要对比新旧数据,可以手动克隆对象,例如用 JSON.parse(JSON.stringify()) 或 lodash.cloneDeep。

让我来写一个更完整的逻辑!我们可以在监听器里手动克隆旧值,这样就能准确地比较变更前后的对象。用 JSON.parse(JSON.stringify()) 比较简单,但如果对象里有函数、循环引用等复杂结构,lodash 的 cloneDeep 会更稳妥。

我来写一个 Vue 3 示例,带完整的变更前后对比

<script setup>
import { reactive, watch } from 'vue'
import cloneDeep from 'lodash/cloneDeep'

// 创建响应式对象
const user = reactive({
  name: 'Alice',
  address: {
    city: 'New York',
    zip: '10001'
  }
})

// 保存初始克隆对象
let oldUserSnapshot = cloneDeep(user)

// 监听对象变更
watch(user, (newVal) => {
  console.log('新值:', newVal)
  console.log('旧值:', oldUserSnapshot)

  // 比较新旧对象
  if (JSON.stringify(newVal) !== JSON.stringify(oldUserSnapshot)) {
    console.log('对象发生了变化!')

    // 遍历差异字段
    for (const key in newVal) {
      if (JSON.stringify(newVal[key]) !== JSON.stringify(oldUserSnapshot[key])) {
        console.log(`字段 "${key}" 发生变化:`)
        console.log('新值:', newVal[key])
        console.log('旧值:', oldUserSnapshot[key])
      }
    }
  } else {
    console.log('对象没有变化')
  }

  // 更新旧值快照
  oldUserSnapshot = cloneDeep(newVal)
}, { deep: true })

// 模拟对象变更
setTimeout(() => {
  user.address.city = 'Los Angeles'
}, 1000)

setTimeout(() => {
  user.name = 'Bob'
}, 2000)

setTimeout(() => {
  user.address.zip = '90001'
}, 3000)
</script>

<template>
  <div>
    <h1>Vue 深层对象变更监听</h1>
    <p>姓名:{{ user.name }}</p>
    <p>城市:{{ user.address.city }}</p>
    <p>邮编:{{ user.address.zip }}</p>
  </div>
</template>

🧠 输出结果

新值: {

    name: 'Alice',
    address: {
        city: 'Los Angeles',
        zip: '10001'
    }
}

旧值: {

    name: 'Alice',
    address: {
        city: 'New York',
        zip: '10001'
    }
}

对象发生了变化! 字段 "address"
发生变化: 新值: {
    city: 'Los Angeles',
    zip: '10001'
}

旧值: {
    city: 'New York',
    zip: '10001'
}

新值: {

    name: 'Bob',
    address: {
        city: 'Los Angeles',
        zip: '10001'
    }
}

旧值: {

    name: 'Alice',
    address: {
        city: 'Los Angeles',
        zip: '10001'
    }
}

对象发生了变化! 字段 "name"
发生变化: 新值: Bob 旧值: Alice 新值: {

    name: 'Bob',
    address: {
        city: 'Los Angeles',
        zip: '90001'
    }
}

旧值: {

    name: 'Bob',
    address: {
        city: 'Los Angeles',
        zip: '10001'
    }
}

对象发生了变化! 字段 "address"
发生变化: 新值: {
    city: 'Los Angeles',
    zip: '90001'
}

旧值: {
    city: 'Los Angeles',
    zip: '10001'
}

🔑 改进要点

  1. 克隆旧值:用 cloneDeep 深拷贝旧对象,避免引用共享。
  2. 字段级对比:逐字段比较,输出具体变更。
  3. 深度监听:deep: true 确保监听对象内部结构变化。 这样就能准确判断对象变化,并知道具体变更的字段!