Vue.js入门 0x6 组件(Component)(3)组件通信
从父组件向子组件通信,通过 props 传递数据就可以了,但 Vue 组件通信的场景不止有这一种,归纳起来,组件之间通信可以用下图表示。
组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信。
自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件。v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。Vue组件有一套模式,子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件。
父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件。
<div id="app">
<p>总数:{{total}}</p>
<my-component
@increase="handleGetTotal"
@reduce="handleGetTotal"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<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>
上面示例中,子组件有两个按钮,分别实现加1和减l的效果,在改变组件的data“counter"后,通过$emit()再把它传递给父组件,父组件用v-on:increase和v-on:reduce(示例使用的是语法糖)。$emit()方法的第一个参数是自定义事件的名称,例如示例的increase和reduce后面的参数都是要传递的数据,可以不填或填写多个。
使用 v-model
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<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>
仍然是点击按钮加 1 的效果 , 不过这次组件$emit()的事件名是特殊的 input, 在使用组件的父级,井没有在<my-component>上使用@input= "handler",而是直接用了 v-model 绑定的一个数据total 。这也可以称作是一个语法糖,因为上面的示例可以间接地用自定义事件来实现。
v-model还可以用来创建自定义的表单输入组件 ,进行数据双向绑定
实现这样一个具有双向绑定的 v-model 组件要满足下面两个要求 :
•接收一个 value 属性。
• 在有新的 value 时触发 input 事件。
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<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>
非父子组件通信
在实际业务中,除了父子组件通信外,还有很多非父子组件通信的场景,非父子组件一般有两种,兄弟组件和跨多级组件。在Vue.2.x中,推荐使用一个空的Vue实例作为中央事件总线(bus),也就是一个中介。
<div id="app">
{{message}}
<component-a></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
//定义中介bus
var bus = new Vue();
//定义全局组件component-a
Vue.component('component-a',{
template:'<button @click="handleEvent">传递事件</button>',
methods:{
//点击按钮时通过bus把事件on-message发出去
handleEvent:function(){
bus.$emit('on-message','来自组件component-a的内容');
}
}
});
//app收到来自bus的事件,进而在回调里完成自己的业务逻辑
var app = new Vue({
el:'#app',
data:{
message:''
},
//mounted钩子函数
mounted:function(){
var _this = this;
//在实例化时,监听来自bus实例的事件
bus.$on('on-message',function(msg){
_this.message = msg;
});
}
})
</script>
父链
在子组件中,使用 this.$parent 可以直接访问该组件的父实例或组件,父组件也可以通过this.$children 访问它所有的子组件,而且可以递归向上或向下无线访问, 直到根实例或最内层的组件。
<div id="app">
{{message}}
<component-a></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.component('component-a',{
template:'<button @click="handleEvent">通过父链直接修改数据</button>',
methods:{
handleEvent:function(){
//访问到父链后,可以做任何操作,比如直接修改数据
this.$parent.message = '来自组件component-a的内容'
}
}
});
//app收到来自bus的事件,进而在回调里完成自己的业务逻辑
var app = new Vue({
el:'#app',
data:{
message:''
}
})
</script>
尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件紧藕合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只有组件自己能修改它的状态。父子组件最好还是通过props和$emit来通信。
子组件索引
当子组件较多时 , 通过 this.$children 来一一遍历出我们需要的一个组件实例是比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。 Vue 提供了子组件索引的方法,用特殊的属性 ref来为子组件指定一个索引名称。
在父组件模板中,子组件标签上使用 ref 指定一个名称,井在父组件内通过 this.$refs 来访问指定名称的子组件 。
<div id="app">
<button @click="handleRef">通过ref获取子组件实例</button>
<component-a ref="comA"></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.component('component-a',{
template:'<div>子组件</div>',
data:function(){
return {
message:'子组件的内容'
}
}
});
var app = new Vue({
el:'#app',
methods:{
handleRef:function(){
//通过¥refs来访问指定的实例
var msg = this.$refs.comA.message;
console.log(msg);
}
}
})
</script>
$refs只在组件渲染完成后才填充,并且它是非响应式的,它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用$refs。