问题描述
在vue中,有一个父组件和一个子组件,在父组件里有一个变量,在这个变量里有一个属性值和一个回调函数,并且这个变量是通过computed
直接return的,如下:
computed: {
testObj() {
return {
name: '空标题',
cb(option) {
option.name = '标题更改了'
}
}
}
}
在这个父组件中调用子组件并通过prop
的形式将这个对象传递给子组件,如下:
<testChild :testObj="testObj" />
在testChild
这个子组件中,展示这个变量的name
值,并有一个事件会触发这个变量的回调函数,更改这个name
值,如下:
<template>
<p>{{testObj.name}}</p>
<button @click="updateTest">更新</button>
</template>
export default {
name: 'TestChild',
props:{
testObj: {
type: Object,
default: ()=>({})
}
},
mathods: {
updateTest() {
this.testObj.cb(this.testObj)
console.log(this.testObj)
}
}
}
现在的问题是,在子组件触发更新事件后,通过console
打印可以看到testObj
中的name
值已经发生了改变,但是视图并没有更新
原因分析
子组件视图之所以没有根据响应式实现更新跟computed
的实现原理有关,在vue中,可以通过data
、computed
、prop
、watch
的方式为当前实例中的变量添加上响应式(vue2通过defineProperty
方法,vue3通过proxy
,实现原理这里不赘述)。但是他们之间的实现方式有所不同,在vue源码中computed
是计算属性,也就意味着,computed
首先是监听参与计算的变量的变化,进而通知watcher
进行派发更新。如果参与计算的变量没有变化,而主动更改 computed 中的变量或者变量里的属性值,只能触发computed
的setter
方法(前提computed
是通过get
、set
设置的)。而在本示例下,父组件是返回一个完整的testObj
对象,在父组件中没有任何变量参与其中计算,这就使得我们在更改testObj
的属性值时,父组件认为这个testObj
并没有更新不需要触发watcher
进行派发更新。因此无论是在子组件还在在父组件,通过testObj.name
展示的值都无法进行更新。
解决方案
解决这个问题的方法可以有多种
一,可以把这个变量放到data
中而不是computed
里
data() {
return {
testObj: {
name: '空标题',
cb(option) {
option.name = '标题更改了'
}
}
}
}
二、在计算属性中使用变量,通过更改这个变量实现computed更新
data() {
return {
isChange: false
}
}
computed: {
testObj() {
return {
name: isChange ? '标题更改了' : '空标题' ,
cb(option) {
this.isChange = true
}
}
}
}
三、在子组件中通过变量赋值的方法,将该变量重新加上响应式
因为在vue中只要在data()中定义变量,vue就会自动为其添加响应式
<template>
<p>{{testObj_ .name}}</p>
<button @click="updateTest">更新</button>
</template>
props:[
testObj: {
type: Object,
default: ()=>({})
}
],
data() {
return {
testObj_: {}
}
},
created() {
this.testObj_ = this.testObj
}
以上就是相关的优化方案。