今天在开发Vue2项目时,遇到了一个纠结点,就是子组件监听父组件传过来的props:
一般普通的子组件监听,如下代码:
<!-- 父组件 -->
<template>
<child :info="msg"></child>
<button @click="change">改变msg值</button>
</template>
<script>
export default {
return {
data() {
msg: '123'
}
},
methods: {
change() {
this.msg = "456"
}
}
};
</script>
<!-- 子组件child -->
<script>
export default {
props: {
info: {
type: String
}
},
watch: {
info(newVal, oldVal) {
console.log("newVal>>>>>>>", newVal)
console.log("oldVal>>>>>>>", oldVal)
}
}
};
</script>
这种很普通的监听父组件传过来的值,没什么好说的,初始化时可以监听到,即使后来父组件修改了值,在子组件中也可以立即监听到。
虽然props传过来的只是基本类型的值,但是换成对象,也不过就是加上deep:true属性,照样还是能监听到。
但是我今天写项目遇到的是,子组件是循环遍历的,并且数据类型也变成了数组,即:
<!-- 父组件 -->
<template>
<div v-for="item in obj">
<child :info="item.arr"></child>
<button @click="change(item)">改变单个子组件的msg值</button>
</div>
</template>
<script>
export default {
return {
data() {
obj: [
{
arr: ['1','2', '3']
},{
arr: ['4','5', '6']
},{
arr: ['7','8', '8']
}]
}
},
methods: {
change(item) {
item.arr = ['0', '0', '0']
}
}
};
</script>
因为要求是每个子组件显示的内容不一样,所以不能在父组件的data里定义一个变量传给子组件,这会导致所有的子组件都会共用这个变量,导致显示的内容都是一样的,这肯定不行,所以就传了各自的item值,来保证遍历的每一个子组件显示的内容都不一样。
但是这样就导致了后续我在父组件中修改了某个子组件的值,子组件会监听不到!
<!-- 子组件child -->
<script>
export default {
props: {
info: {
type: Array
}
},
watch: {
info(newVal, oldVal) {
console.log("newVal>>>>>>>", newVal) // 初始化的时候会监听到,后续父组件修改值不会变
console.log("oldVal>>>>>>>", oldVal)
}
}
};
</script>
一开始我也怀疑是数据类型变了,变成了数组,所以要用deep监听,可试了还是没用:
props: {
info: {
type: Array
}
},
watch: {
info: {
handler(newVal, oldVal) {
console.log("newVal>>>>>>>", newVal) // 初始化的时候会监听到,后续父组件修改值不会变
console.log("oldVal>>>>>>>", oldVal)
},
deep: true
}
因为上面代码是简化版,所以有的读者应该已经知道问题在哪了,我当时是代码太多一时给绕进去了,还一度怀疑是Vue2的defineProperty辣鸡!哈哈
归其根本原因,是我父组件里修改的item值不是响应式的! 也就是说,不管我后来怎么改item的值,都没有触发外面真正响应式数据obj的更新!也就是父组件的template模板根本就一点反应没有(没有更新),那模板里的子组件从哪获取新值?对吧
子组件在初始化的时候,在mounted生命周期过后,页面的watch便不会再执行了,除非触发到父组件的页面更新,再来更新子组件!
所以知道原因后,代码就主要来解决如何更新父组件页面刷新!
大概有三个方案:
第一个是利用this.$set()
this.$set(obj, index, val) // 修改哪条就更新哪条,同时会触发页面更新
第二个最简单直接:forceUpdate
change(item) {
item.arr = ['0', '0', '0']
this.$forceUpdate(); // 修改完值后,强制重新渲染视图,简单粗暴
}
第三个比较优雅:key
<template>
<component-to-re-render :key="componentKey" />
</template>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
当key
发生更改时,vue会删除旧组件并创建新组件,它将重新初始化自身并“重置”其状态