Vue3.2父子组件间ref数组监听:为什么子组件watch中监听父组件ref数组需要使用箭头函数?
在Vue3.2开发中,父子组件通信是常见的场景。当父组件通过ref传递数组给子组件,并在子组件中监听该数组变化时,很多开发者会遇到一个困惑:为什么必须使用箭头函数来定义watch回调?本文将深入解析这个问题。
问题重现
假设我们有如下父子组件结构:
父组件传递ref数组:
<template>
<ChildComponent :items="itemsRef" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const itemsRef = ref([1, 2, 3])
</script>子组件接收并监听:
<template>
<div>{{ items }}</div>
</template>
<script setup>
import { watch } from 'vue'
const props = defineProps({
items: {
type: Array,
required: true
}
})
// 这样监听可能不会触发
watch(props.items, (newVal) => {
console.log('数组变化:', newVal)
})
</script>问题分析
上述代码中,子组件的watch可能无法正确监听到父组件ref数组的变化。原因在于:
props.items是响应式对象的属性:props本身是一个响应式对象,props.items是对ref数组的引用
直接监听对象属性:watch默认情况下不会深度监听对象属性的变化
引用不变性:当父组件修改数组内容时,props.items的引用可能保持不变
解决方案:使用箭头函数
正确的做法是使用箭头函数来返回要监听的值:
<script setup>
import { watch } from 'vue'
const props = defineProps({
items: {
type: Array,
required: true
}
})
// 使用箭头函数返回要监听的值
watch(
() => props.items,
(newVal, oldVal) => {
console.log('数组变化:', newVal)
// 注意:这里oldVal可能与newVal相同,因为数组引用没变
},
{ deep: true } // 深度监听
)
</script>为什么箭头函数是关键?
1. 创建响应式依赖
箭头函数() => props.items创建了一个新的函数作用域,使得watch能够正确追踪props.items的变化。每次props.items变化时,这个函数会重新执行,返回新的值。
2. 解决引用不变性问题
当父组件修改数组内容时:
// 父组件中 itemsRef.value.push(4) // 数组内容变了,但引用没变
此时直接监听props.items无法检测到变化,因为引用地址相同。箭头函数每次都会读取最新的props.items值。
3. 配合deep选项
由于数组内部元素变化不会导致引用变化,需要设置{ deep: true }来深度监听数组内部变化。
完整示例对比
错误写法:
// 无法监听到数组内部变化
watch(props.items, (newVal) => {
console.log('不会触发')
})正确写法:
// 能正确监听数组变化
watch(
() => props.items,
(newVal) => {
console.log('触发了', newVal)
},
{ deep: true }
)注意事项
oldVal的限制:由于数组引用可能不变,oldVal可能与newVal指向同一个数组
- :深度监听会带来性能开销,只在必要时使用
替代方案:考虑使用computed或emit事件来优化数据流
总结
在Vue3.2中,子组件监听父组件传递的ref数组时,必须使用箭头函数() => props.items的原因是:
创建正确的响应式依赖追踪
解决数组引用不变但内容变化的问题
配合deep选项实现深度监听
这种模式确保了我们能够准确捕获到数组的各种变化,无论是引用变化还是内部元素的变化。