Vue基础之非父子组件间传值

1,699 阅读4分钟

运用的场合

我们已经知道,从父组件向子组件传值可以通过在子组件中添加props实现,而子组件向父组件传值主要通过向外触发函数。

当我们的页面采用三层嵌套,颜色由浅到深并对应图中1,2,3。

那么如果我们想沿着蓝色的线传递值,是否需要将值从1-> 2-> 3这样;如果我们想沿着紫色的线传递值,是否要将值从3 ->2 ->1 ->2 -> 3呢。这些过于麻烦,都不需要!

我们需要使用: Bus/总线/发布订阅模式/观察者模式(反正都是一个意思) ,便可以实现非父子关系的传值。

基本格式:

<body>
	<div id="root">
		<child></child>
		<child></child>
	</div>

	<script type="text/javascript">
		Vue.component('child',{
			template:'<div>child</div>'
		})

		var vm=new Vue({
			el:'#root'
		})
	</script>
</body>

这里我们先写入一个最简单的父子组件传值。

<div id="root">
        <child content='Aston'></child>
        <child content='Martin'></child>
</div>

<script type="text/javascript">
    Vue.component('child',{
        props:{
            content:String
        },
        template:'<div>{{content}}</div>'
    })

    var vm=new Vue({
        el:'#root'  
    })
</script>

用一个例子实现传值

我现在想实现如果点击任何一个文字,另一个文字也会变成我点击的文字,其实就是改变未点击文字的content值。这就属于非父子组件的兄弟组件传值。

1. 在组件中添加基本的点击事件

Vue.component('child',{
    props:{
        content:String
    },
    template:'<div @click="handleclick">{{content}}</div>',
    methods:{
        handleclick:function(){
            alert(this.content)
        }
    }
})

2. 添加总线属性:

Vue.prototype.bus = new Vue()

这里是给Vue的每个prototype上挂载一个bus属性,然后将bus属性指向vue实例。由于我们的每一个实例都是通过Vue创建的,因此我们每个组件上都会有bus属性。

3. 修改点击事件

handleclick:function(){
    this.bus.$emit('change',this.content)
}

由于第一步已经给所有实例添加了bus属性,这里调用bus$emit 方法向外(总线)触发事件“change”,并携带参数“this.content”。

这个bus顾名思义,可以理解为“公交车/总线”,作为一个公共区域被多方监听并触发多个事件。

这里举个栗子来理解:将子组件相当于病房,父组件相当于护士台,但是不方便的是,病房之间传递东西(参数)需要先呼叫护士台,拿到护士台后再送去别的病房。
现在我们使用总线,类似使用医院走廊,一个病房需要传给隔壁病房一个东西时候,在走廊嚎一嗓子(事件触发),隔壁病房监听(事件监听)到走廊的声音,出去取东西即可。

4. 使用生命周期钩子

mounted:function(){
    this.bus.$on{this.bus.$on('change',function(msg){
        this.content = msg;
    })
}

mounted就是实例被挂载的时候执行的函数,并在这个函数中实现监听bus上触发的事件。

但注意,这个方法加入后期望效果仍不能实现!

原因是:我们使用了两个子组件,并且都具有“点击向总线触发”和“监听总线”这两个方法。所以如果我点击第一个组件,他向外触发事件的同时进行监听,这就会导致this分别指向各自本身,第一个组件的this都指向自己,第二个组件同样。这样就没法做到把第一个this指向的值传递给第二个组件。

5. 记录下点击的this

我们在mounted函数中进行修改:

mounted:function(){
    this.bus.$on{this.bus.$on('change',function(msg){
    var loc=this
        loc.content = msg;
    })
}

我们在mounted(已挂载函数)函数中声明一个变量“loc”用以保存我点击的那个this

这样我们的效果就实现了,但是当我们打开控制台,会发现报错:避免子组件改变父组件的值。也就是说我们一直在改变父组件所传递的content,这是不应该的。

6. 保存一份父组件的值

由于上面的报错,我们在组件中添加data来保存父组件的值:

data:function(){
    return {
        selfcontent:this.content
    }
}

并将其他部分的content进行替换。

7. 全部代码

<body>
	<div id="root">
		<child content='Aston'></child>
		<child content='Martin'></child>
	</div>

	<script type="text/javascript">

		Vue.prototype.bus = new Vue()

		Vue.component('child',{
			data:function(){
				return {
					selfcontent:this.content
				}
			},
			props:{
				content:String
			},
			template:'<div @click="handleclick">{{selfcontent}}</div>',
			methods:{
				handleclick:function(){
					this.bus.$emit('change',this.selfcontent)
				}
			},
			mounted:function(){
				var loc=this;
				this.bus.$on('change',function(msg){
					loc.selfcontent=msg;
				})
			}
		})

		var vm=new Vue({
			el:'#root'
		})
	</script>
</body>

总结

我们最后再捋一遍整个过程:我们点击第一个文字盒子,触发组件内“handleclick”事件,此事件向总线触发“change”事件并将第一个盒子中的内容作为参数传递,这时两个盒子都执行mounted函数,将指向各自组件的this保存在变量loc中,然后监听的“change”事件被触发,将事件所携带的参数(指向第一个盒子的内容)赋给指向各自的内容。第一个盒子赋给自己不变,第二个盒子实现第一个盒子的值赋给自己。