组件间的关系可分为父子组件通信、兄弟组件通信、跨级组件通信。
子组件向父组件传递数据
自定义事件:
使用 $on()和 $emit()来实现通信
v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。
子组件用 $emit()来触发事件,父组件用 $on()来监听子组件的事件。 $emit()方法的第一个参数是自定义事件的名称,后面的参数是要传递的数据,可以不写,或者写多个。
父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件。
例:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
<body >
<div id="app">
<p>总数:{{ total }} </p>
<my-component
@increase="handleGetTotal"
@reduce="handleGetTotal"></my-component>
</div>
</body>
<script>
Vue.component('my-component', {
template: `
<div>
<button @click="handleIncrease">+1</button>
<button @click="handleReduce">-1</button>
</div>
`,
data:function ( ) {
return {
counter:0
}
},
methods:{
handleIncrease:function(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce:function(){
this.counter--;
this.$emit('reduce',this.counter);
}
}
});
var app=new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleGetTotal:function(total){
this.total=total;
}
}
})
</script>
</html>
示例中,以点击+1按钮为例,子组件的+1按钮点击后,触发handleIncrease事件。而在子组件内的methods中,handleIncrease事件内使用$emit()触发了increase事件,并且将此时的counter传入increase这个事件内,父组件用v-on:increase监听该自定义事件。在实例的方法中handleGetTotal()函数接收到由子组件传递过来的total,并将total赋值给实例中data的total。
v-model指令
v-model可以在自定义指令上使用,v-model绑定父组件中的data数据。实例:
<div id="app">
<p>总数:{{ total }} </p>
<my-component v-model="total"></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<button @click="handleClick">+1</button>',
data:function( ){
return {
counter:0
}
},
methods:{
handleClick:function(){
this.counter++;
this.$emit('input',this.counter);
}
}
});
var app = new Vue({
el:'#app',
data:{
total:0
}
});
</script>
这段代码,在组件方法中的$emit()的事件名是input,并没有在< my-component >上使用@input=“hander”,直接使用了v-model绑定了total.这是一个语法糖。
v-model 还用来创建自定义的表单输入组件,实现数据双向绑定。
要求:
1.接收一个value属性
2.在有新的value时触发input事件
例:
<div id="app">
<p>总数:{{ total }}</p>
<my-componet v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script>
Vue.component('my-component',{
props:['value'],
template:'<input :value="value" @input="updateValue">',
methods:{
updateValue:function (event){
this.$emit('input',event.target.value);
}
}
});
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleReduce:function(){
this.total--;
}
}
})
</script>
非父子组件之间传递数据
中央事件总线(bus)
非父子组件之间传递数据其实就是靠“中介”,这就是中央事件总线,在Vue中推荐使用一个空的Vue实例来作为中央事件总线。中央事件总线起到了一个中转站的作用
例:
<div id="app">
<{{ message }}>
<component-a></component-a>
</div>
<script>
var bus = new Vue( );
Vue.component('component-a',{
template:'<button @click="handleEvent">传递事件</button>',
methods:{
handleEvent:function(){
bus.$emit('on-message','来自组件component-a的内容');
}
}
});
var app = new Vue({
el:'#app',
data:{
message:''
},
mounted:function(){
var _this=this;
//在实例初始化时,监听来自bus实例的事件
bus.$on('on-message',function(msg){
_this.message = msg;
});
}
})
</script>
在例子中创建了一个bus的空vue实例,然后全局定义了组件component-a,在创建vue实例app。在app初始化的时候,在生命周期mounted钩子函数里监听了bus的事件on-message,在组件component-a中,创建了点击事件,点击按钮,通过bus将事件on-message发送出去,在app中使用bus.$on()接收事件,在回调中完成自己的业务逻辑。这样就完成了组件间的通信。
使用中间事件总线可以实现任何组件间的通信。可以深入使用bus实例,给他添加data,methods,computed等选项。
父链
在子组件中,使用this.$ parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$ children访问他的所有子组件,可以递归向上或向下无限访问,直到根实例或最内层的组件。
例:
<div id="app">
<{{ message }}>
<component-a></component-a>
</div>
<script>
Vue.component('component-a',{
template:'<button @click="handleEvent">通过父链直接修改数据</button>',
methods:{
handleEvent:function(){
//访问到父链后,可以任何操作
this.$parent.message = '来自组件的内容';
}
}
});
var app = new Vue({
el:'#app',
data:{
message:' '
}
})
</script>
虽然这样可以在父子组件之间完成通信,但是子组件尽可能的避免以来父组件的数据,更不应该主动修改他的数据。父子组件最好还是通过props和$ emit来通信。
子组件索引
当子组件较多时,适合用子组件索引来完成通信。Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称。
例:
<div id="app">
<button @click="handleRef">通过ref获取子组件实例</button>
<component-a ref="comA"></component-a>
</div>
<script>
Vue.component('component-a',{
template:'<div>子组件</div>',
data:function(){
return {
message:"子组件内容"
}
}
});
var app = new Vue({
el:'#app',
methods:{
handleRef:function(){
var msg = this.$refs.comA.message;
console.log(msg);
}
}
})
</script>
在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$ refs来访问指定名称的子组件。$ refs只有在组件渲染完成后才填充,并且他不是响应式的。应当避免在模板或计算属性中使用$ refs。