重点啊!!当初学这个时候,纠结了好一会儿。
先来看看组件关系。
可以把一个网页拆分成很多部分,如图所示,可以理解为一个组件,分成三个部分,顶部、左侧、右侧三个区域。也就是将1号组件再拆分成3个2号子组件,左侧区域进一步细化,可拆分成2个3号子组件,右侧进一步细化,可拆成3个3号子组件。
第二层的组件想和第一层的组件通信,也就是2号想和1号通信,
父组件通过props,和子组件传值;子组件通过事件派发和父组件传值。
那要是第三层组件想和第一层组件通信呢?可以直接通过属性传递吗?这是不行的。那能第一层先和第二层,第二层再和第三层通信,这样也算是第一层和第三层传递。
不过这种传值略显复杂,
如果三号中的第一个想和三号中的最后一个传值呢?
一步步传上去,再一步步传下来?
(;´д`)ゞ
有点晕啊,在Vue
之中,像这样非父子组件间的传递(1和3这种爷爷孙子之类的,还有3和3这种兄弟之间的,反正就不是父子之间的),我们可以通过Vuex
来解决。还有一种是通过发布订阅模式(观察者模式)来解决,在Vue
中叫做总线机制。
先new
一个Vue
的实例,挂载在Vue.prototype.bus
上,也就是Vue的prototype上挂载一个bus属性,这个属性指向Vue
的实例,只要我们之后调用new Vue
或者创建组件的时候,每一个组件都会有bus这个属性,因为每个组件或者每一个Vue
的实例都是通过Vue
这个类来创建的, 而之前在Vue
的类上挂载了一个bus属性,而且是挂在了prototype
上, 所以每个通过这个类创建的对象,也就是实例上都有bus属性,它都指向同一个Vue
实例。
在这里我们希望当子组件被点击的时候,另一个子组件也相应的更改。怎么将子组件1的内容传递给子组件2呢?
this.bus.$emit('change', this.content)
分解来看,this.bus指的是什么?
指的就是实例上挂载的bus
,这个bus
又是Vue
实例,所以它上面就有$emit
这个方法,所以就能通过这个方法来派发change
事件。
有了派发事件,相应的其他组件就要进行监听。
在这里借助生命周期钩子(不记得的看生命周期那章)
这里借助的是mounted
,也就是这个组件被挂载的时候,它会执行的一个钩子函数,在这里让组件去监听bus
的改变,这个组件一定会有bus
属性,在它上面也一定有$on
这个方法,它就能监听到bus
上派发的事件,也就是 $emit
派发的change
事件,这里监听到change
事件之后,alert一下
,每次点击,都会有2次弹窗。
为什么会有两次弹窗?
因为一个child
组件内去触发事件时,两个child
组件都进行同一个事件的监听,所以两个组件都会去弹窗,所以有两次弹窗。
还没有结束,在
mounted
执行的$on
内,this
的作用域发生了改变,所以要将this
作用域进行保存,也就是var this_ = this;
,下面使用的是保存的this_
,但是会报出一个警告。
因为child里的content是由父组件传递过来的。子组件是不能强行修改这个内容的。
同样自己定义一个副本,然后修改副本数据就行了。
非父子组件传值就解决啦。不论是兄弟组件还是什么其他非父子组件的通信,比较简单的都可以用这种来解决,复杂的用的是vuex
2019/06/14更新
对于评论中出现的“点击之后,只能修改一次”这个,可以看看虚拟Dom,它将新的对象树与旧的对象树进行对比,记录差异,渲染的是差异部分。对于DOM的操作只变更有不同的地方。
回到问题上,当子组件1的值传递给子组件2,对子组件2的值进行修改之后。此时两个组件的值应该是一致的。那么在第二次点击的时候,发现内容并没有新的变化,也就是没有差异,dom并不会进行新的操作。看起来也就只能点击一次。不确定的可以加一行代码。
至于为什么会是两次弹窗,在上面已经有解释啦。
2020/03/31更新
provide 和 inject 2.2.0 新增
这两个为了提供更好的跨组件通信方案。props
是一层层向下传,vuex
会增加学习、使用成本。而这个会使得组件通信更加简单。
点击change按钮变成
分析一下这个例子:
- 先是全局定义了一个Demo组件,这个Demo组件中有一个
message
,另外还有一个Demo2组件 - 这个Demo组件中定义了一个change方法,用来更改
message
的内容 - 然后定义了一个Demo2组件,这个组件内包含了Demo3组件
- 接下来定义了Demo3组件,这个组件中有一个按钮,这个按钮会调用changeMessage方法,这个方法又会调用父组件的方法来改变
message
- 最后渲染到页面上。
- Demo组件用了
provide
,将自身this,打包成了elDemo属性,提供给子组件进行调用。也就是子组件可以通过elDemo来找到父组件的实例,不需要通过props一层层传递之类的。 - 直接子组件Demo2并没有进行任何操作,而Demo3内通过
inject
,这是一个数组类型,在数组内标识需要引入的父组件实例。也就是elDemo。 - 可直接通过this.elDemo来调用父组件实例,父组件上有一个change方法,所以this.elDemo.change()是直接调用了父组件的change方法,并且向内传入组件名称。
- 刚开始时这个组件名称是Demo,而点击之后就会变成了Demo3。