此文写于:2021-4-6。
一、起因
今天,在写一个小组件时,发现通过父组件传给子组件的props的值除了第一次mounted时能够正常获取外,在mounted后点击分类切换请求时,始终获取不了父组件传过来的值。
二、问题
1.问题描述:
我想要实现的是父组件通过props给子组件传值。
父子组件如下:子组件是一个留言点赞的小组件,就是下图中蓝色区域的组件,由于这个小组件需要多个地方用到,所以把它封装成小组件。父组件是外面一整个区域。
但是这个小组件只有在mounted时请求到的数据是对的,其他时候时错的,就是点击分类时获取不到数据,由于这个小组件需要获取父组件传来的文章id然后去数据库里拿点赞数和留言数,但是奇怪的是第一次正常获取文章id,第二次获取不了,所以导致试图无法更新:
分类按钮如下:(此时是realtime分类区域:可以看到该分类第一条的点赞留言数分别是1,3,此时是正常的数据获取)
但是当我切换分类时:(可以看到切换到subject下,此时留言点赞数还是没变,按道理这个subject的正确数值都是0,然后由于我做了处理,如果点赞留言都是0的话,数字就不显示,但是这里仍然显示1,3)
2.提出问题:
为什么在第一次mounted时,父组件传过来的数据就是对的,然后等到切换分类时的数据请求就是错的?
3.分析问题:
我思考了一下发现普通的父组件把组件传给子组件的props时应该是这样的:
mounted时:
(1)无论是在mounted时请求数据,还是在created请求数据时,此时的父组件运行到mounted的生命周期时,数据此时已经获取完毕。
(2)然后父组件此时在mounted传给子组件的props此时数据是正常的,因为父组件的dom元素创建完毕后,子组件此时的dom元素已经完全创建完毕,打个比方就是父组件相当于一个刚出生的婴儿,而子组件就相当于器官,在婴儿还没出生时,里面的器官(子组件)就会逐渐创建,当全部器官创建完毕之后,婴儿可以出生(父组件创建完毕)。
(3)此时父组件出生后,所以会把获取的数据传给子组件。子组件正常获取。
mounted后:
(1)mounted后,父组件请求的数据获取前:
此时每次点击一次分类,父组件都会重新请求一组数据,然后此时由于父组件已经出生了,子组件也早已创建完毕,所以当父组件请求网络数据还没获取值时,子组件的数据早已经有了值,这些值就是mounted时传给子组件的那些数据,所以子组件直接拿来用了。
(2)mounted后,父组件请求的数据获取后:
父组件拿到新的数据,会传给子组件,但是传给子组件的props时,子组件的视图层已经渲染完毕,虽然此时的props里面数据会重新变化,但是视图层的数据还是mounted时父组件给他传的那些数据,不会响应式变化,所以导致出现了错误。
4.解决问题:
既然我们props实际上是可以接收到父组件的,那么只要实时监听子组件里对应的props变化,然后再根据这个变化,做出相应的动作,不就可以完成子组件试图的更新吗?
所以解决代码:
watch: {
topic_id(newValue, oldValue) {
this.topic_id = newValue;
console.log("newvalue:", newValue);
// 异步处理,由于父组件mainbox传值是一个异步过程,所以需要watch这个变量,一旦变量改变,则重新发送请求计算留言总数
gettotal_agree_comment_reply(this.topic_type, this.topic_id).then(
(res) => {
this.totalAgrees = res[0].totalAgrees; //点赞总数赋值
this.totalcp = res[0].totalComment_Reply; //评论及回复总数的和
// console.log("总留言与点赞:", res);
}
);
},
},
此时再切换分类时,数据正常请求。
注:此场景的父子组件通过props是分类下的场景,就是父组件的数据会多次发生变化,而不是只发生一次变化,类似分类的还有分页场景,但是我统一把它称为父组件数据动态变化场景,类似此种场景都需要监听props的数据变化。
5.后话:
本来我是想要用computed的,因为vue官方不是一直在推荐computed来代替watch吗,但是我觉得此种情境下computed好像不会触发:
因为vue官方下面有这样一段话:
就是说computed属性只会在响应式数据发生变化时,才会实时变化,但是如果这个数据不是响应式的,那么就发生不了变化,详细的响应式数据解释,你可以访问官网:vue响应式原理,我这里仍做一下简单的记录。
vue中何种类型数据为响应式:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
对于对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data
对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
上面巴拉巴拉一大堆:
通读一下,主要告诉vue的使用者:
(1)关于每一个创建的组件,vue内部会开启一个自定义的内部函数watcher去实现它的响应式。
(2)vue响应式数据是定义在data里面的变量,如果你定义在data外面的变量就不是响应式的。
所以我们通过props传给子组件的值不是data里面的数据,不属于响应式的,而computed是依赖于响应式的,即使props里的数据发生变化,computed仍然检测不到,所以此处只能使用watch。
完毕。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。