我们在之前的文章中提到过,父组件向子组件传值时是利用定义属性来传值的,而子组件向父组件传值是通过事件,那么下面的情况,同级组件或是隔代组件,即非父子组件之间的传值是怎么进行的呢(如下图)?
一般来说我们有两种解决非父子组件传值的问题:
- 发布订阅模式/观察者模式/bus/总线机制
- vuex
在这里我们主要对总线机制进行一下学习,关于vuex的内容随后学习补充。
– 首先创建一个HTML文件,并创建父子组件,
<html>
<head>
<meta charset="UTF-8">
<title>非父子组件传值</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<child></child>
<child></child>
</div>
<script>
//子组件
Vue.component('child', {
template: '<div>child</div>'
});
var vm = new Vue({
el: '#root'
})
</script>
</body>
</html>
页面效果如下:
我们希望子组件显示内容是外部传递进来的,这里涉及到了父到子的参数传递:
<div id="root">
<child content="hello"></child>
<child content="world"></child>
</div>
– 然后子组件通过props进行接收并做一个参数校验,传来的content的值必须为String类型,接下来我们就可以在子组件中显示content的内容了:
//子组件
Vue.component('child', {
props: {
content: String
},
template: '<div>{{content}}</div>'
});
页面效果:
– 接下来进一步实现效果点击hello,world也变成hello,反之亦然。这里就涉及到非父子组件之间的传值了,上面的子组件被点击的时候,要将hello传值给下面同级的子组件(兄弟组件),反之亦然。
– 首先我们new一个vue的实例,然后将Vue.prototype上的bus属性指向这个实例,这样我们每次调用Vue或者是创建组件的时候,组件上面都会挂载bus这个属性:
Vue.prototype.bus = new Vue();
– 接下来在子组件中绑定一个点击事件handleClick,并在methods中定义该事件:
Vue.component('child', {
props: {
content: String
},
template: '<div @click="handleClick">{{content}}</div>',
methods:{
handleClick: function () {
alert(this.content);
}
}
});
– 接下来要将点击的组件的参数传递给另外一个组件,bus是vue的实例,所以也具有$emit()这个方法,我们可以通过bus来向外派发一个事件,同时携带了参数this.content,就是点击的组件的内容:
handleClick: function () {
this.bus.$emit('change',this.content)
}
– 其他组件要对这个事件进行监听,我们借助一个生命周期钩子:
mounted: function () {
this.bus.$on('change', function (msg) {
alert(msg);
})
}
该组件的值会弹出两次,这是因为在一个child组件中触发事件的时候,两个child组件都会进行监听,所以两个子组件都会弹出弹框。
– 这个时候我们只需把监听的子组件内容变成msg:
mounted: function () {
this.bus.$on('change', function (msg) {
this.content = msg;
})
}
但是我们会发现点击根本没有效果,这是因为function中this的作用域发生了变化,需要将this保存:
mounted: function () {
var that=this;
this.bus.$on('change', function (msg) {
that.content = msg;
})
}
– 但是点击控制台出现了警告信息:
我们在之前说过单向数据流的问题,子组件不能对父组件传来的参数直接进行操作,最好创建一个副本:
data:function() {
return {
name:this.content /*定义一个content的副本name,单向数据流*/
}
},
然后将所有的content都换成name。
最后附上完整的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>非父子组件传值的问题(Bus/总线/发布订阅模式/观察者模式)</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<child content="dell"></child>
<child content="lee"></child>
</div>
<script>
/*需求:点击Dell时会变成Lee,点击Lee时会变成Dell*/
Vue.prototype.bus=new Vue() /*每个实例上都会挂载一个bus属性,bus又是一个vue实例*/
Vue.component('child',{
data:function() {
return {
name:this.content /*定义一个content的副本name,单向数据流*/
}
},
props:{
content:String
},
template:'<div @click="handleClick">{{name}}</div>',
methods:{
handleClick:function () {
this.bus.$emit('change',this.name)
}
},
mounted:function () { /*元素挂载到页面上才会执行这个函数*/
var that=this /*此时this的作用域发生了变化*/
this.bus.$on('change',function (msg) { /*能够监听到bus上发布的事件*/
/* alert(msg) /!*会弹出两次,因为在一个child组件上触发事件的时候,
两个组件都进行了同一事件的监听*!/*/
that.name=msg
})
}
})
var vm=new Vue({
el:"#root",
})
</script>
</body>
</html>
这样通过一个bus总线就实现了非父子组件中的传值。