Vue 组件通信

Vue 组件通信

前一篇文章中总结了父组件通过props向子组件传递数据的内容,本篇文章总结一下其他的组件通信的情况:子组件向父组件传递数据,兄弟组件通信,跨级组件通信等。

(1)自定义事件

当子组件向父组件传递数据的时候,就用到了自定义事件:子组件用$emit()来触发事件,父组件通过v-on来监听子组件触发的事件,下面是一个示例:

<div id="app">
    <test v-on:change1="add">test>
    <div>abc的值为:{{abc}}div>
div>
<script>
    Vue.component('test', {
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(){ this.abc++ } } })
script>

首先在定义的子组件内,通过v-on:click="$emit(‘change1’)"向父组件传递消息,也就是,在点击子组件中的

元素时触发了change1事件,同时父组件用v-on在上监听这个事件:

 <test v-on:change1="add">test>

当监听到这个事件时就会触发父级组件中的add方法,add方法会让父级组件中的abc的值加1。

1.通过事件向父组件发送消息时带参数
<div id="app">
    <test v-on:change1="add($event)">test>
    <div>abc的值为:{{abc}}div>
div>
<script>
    Vue.component('test', {
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(value){ this.abc+=value; } } })
script>

上面代码中,子组件通过事件change1向父级组件发送消息时带了参数5,在监听这个事件的时候,可以通过$event访问到这个传递的参数:

<test v-on:change1="add($event)">test>

在触发父组件的add方法时,这个值将会作为第一个参数传入这个方法。

   add:function(value){
                this.abc+=value;
   }

上面的代码中,子组件向父级组件发送消息时带了参数5,这个参数最终被传递给了父级组件的add方法中,使得将父级组件的abc的值增加5。
上面的代码中,不通过$event,add方法也可以自动获取到传递的参数。

<div id="app">
    <test v-on:change1="add">test>
    <div>abc的值为:{{abc}}div>
div>
<script>
    Vue.component('test', {
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(value){ this.abc+=value; } } })
script>

在自定义事件中可以不传参数,也可以传递多个参数。

另外需要注意的是,v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:ChangeOne将会变成 v-on:changeone——导致 ChangeOne 不可能被监听到:

<div id="app">
    <test v-on:ChangeOne="add">test>
    <div>abc的值为:{{abc}}div>
div>
<script>
    Vue.component('test', {
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(value){ this.abc+=value; } } })
script>

运行上面的代码会发出警告:
在这里插入图片描述
所以在命名自定义事件的名称的时候最好采用小写加短横线分割的命名方式。

2.native修饰符

如果想在父组件中给子组件绑定一个原生的DOM事件,可以这样写:

<div id="app">
    <test v-on:click="add">test>
    <div>{{abc}}div>
div>
<script>
    Vue.component('test', {
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(value){ this.abc+=value; } } })
script>

如果想让v-on绑定的click事件触发,就要在子组件内通过$emit触发click事件。这种情况下的click事件相当于自定义事件,而并不是DOM原生事件。

在子组件内如果没有通过$emit触发click事件,而是给组件直接绑定原生的DOM事件是没有办法触发的:

<div id="app">
    <test v-on:click="add">test>
    <div>{{abc}}div>
div>
<script>
    Vue.component('test', {
        props: ['data'],
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(){ this.abc++; } } })
script>

上面的代码点击之后是没有效果的。
而.native修饰符可以解决这个问题,表示监听组件根元素的原生DOM事件:

<div id="app">
    <test v-on:click.native="add">test>
    <div>{{abc}}div>
div>
<script>
    Vue.component('test', {
        props: ['data'],
        template: `
                
点击这里向父级组件传递消息!
`
}); var app = new Vue({ el: "#app", data: { abc:0 }, methods:{ add:function(){ this.abc++; } } })
script>

上面的代码可以正常监听原生的DOM的click事件,触发add事件。

(2)非父子组件通信——中央事件总线

在Vue.js 2中推荐使用一个空的Vue实例作为中央事件总线(bus),来进行非父子组件的通信:兄弟组件,跨多级组件。

<div id="app">
    <test-1>test-1>
    <test-2>test-2>
div>
<script>
    var bus = new Vue();
    Vue.component('test-1', {
        template: `
我是test-1组件!
`
, methods: { handle: function () { bus.$emit("msg", "来自组件test-1的消息!") } } }); Vue.component('test-2', { template: `
message:{{message}}
`
, mounted: function () { var _this = this; bus.$on("msg", function (value) { _this.message = value; }) }, data: function () { return { message: "" } }, }); var app = new Vue({ el: "#app", })
script>

首先创建了一个名为bus的空Vue实例。之后定义了两个全局组件test-1和test-2。在组件test-1中点击时会通过bus触发事件msg,同时也会把参数"来自组件test-1的消息!“传递出去。组件test-2中,在生命周期mounted钩子函数中监听了来自bus的事件msg,触发了回调函数将组件test-2中的message变为"来自组件test-1的消息!”。

上面代码中,在组件test-2中监听来自bus的事件时需要绑定this,其中this指向的是名为bus的空Vue实例,并不是指向test-2组件。
把回调函数改为箭头函数,其中的this就指向test-2组件了:

<div id="app">
    <test-1>test-1>
    <test-2>test-2>
div>
<script>
    var bus = new Vue();
    Vue.component('test-1', {
        template: `
我是test-1组件!
`
, methods: { handle: function () { bus.$emit("msg", "来自组件test-1的消息!") } } }); Vue.component('test-2', { template: `
message:{{message}}
`
, mounted: function () { bus.$on("msg", (value) =>{ this.message = value; }) }, data: function () { return { message: "" } }, }); var app = new Vue({ el: "#app", })
script>

(3)非父子组件通信——父链和子组件索引

在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,
父组件也可以通过this.$children访问它的子组件。

1.父链
<div id="app">
    <test-1>test-1>
    <div>message:{{message}}div>
div>
<script>
    Vue.component('test-1', {
        template: `
点击改变父组件中的message!
`
, methods: { handle: function () { this.$parent.message = "来自test-1组建的信息!" } } }); var app = new Vue({ el: "#app", data:{ message:"" } })
script>

虽然可以在子组件中改变父组件的数据,但是最好不要这样做。
理想情况下,只有组件自己可以修改自己的数据状态。

2.子组件索引

先来看一下this.$children的示例,要注意this.$children返回的是一个数组:

<div id="app">
    <div v-on:click="handle">
        点击这里获取子组件的message:{{msg}}
    div>
    <test-1>test-1>
div>
<script>
    Vue.component('test-1', {
        data: function () {
            return {
                message: "我是test-1组件!"
            }
        },
        template: `
`
, }); var app = new Vue({ el: "#app", data: { msg: "" }, methods: { handle: function () { var a = this.$children[0].message; this.msg=a; } } })
script>

当子组件较多时,通过this.$children一一遍历出我们需要的组件是比较麻烦的。
Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称:

<div id="app">
    <div v-on:click="handle">点击这里获取子组件的message:{{msg}}div>
    <test-1 ref="A">test-1>
div>
<script>
    Vue.component('test-1', {
        data: function () {
            return {
                message: "我是test-1组件!"
            }
        },
        template: `
`
, }); var app = new Vue({ el: "#app", data: { msg: "" }, methods: { handle: function () { var a = this.$refs.A.message; this.msg = a; } } })
script>

在子组件标签上使用ref指定一个名称(这里是A),在父组件内通过this.$refs来访问指定名称的子组件。

(4)在自定义组件上使用v-model

<div id="app">
    <div>{{total}}div>
    <abc v-on:input="change">abc>
div>
<script>
    Vue.component('abc', {
        template: ``,
        data: function () {
            return {
                count: 0
            }
        },
        methods:{
            handle:function(){
                this.count++;
                this.$emit("input",this.count)
            }
        }
    });
    var app = new Vue({
        el: "#app",
        data: {
            total: 0
        },
        methods:{
            change:function(value){
                this.total=value;
            }
        }
    })
script>
<div id="app">
    <div>{{total}}div>
    <abc v-model="total">abc>
div>
<script>
    Vue.component('abc', {
        template: ``,
        data: function () {
            return {
                count: 0
            }
        },
        methods:{
            handle:function(){
                this.count++;
                this.$emit("input",this.count)
            }
        }
    });
    var app = new Vue({
        el: "#app",
        data: {
            total: 0
        }
    })
script>

上面两种写法是等价的,第二种写法直接用v-model=“total”,代替了v-on:input="change"绑定的方法。v-model相当于一个语法糖。

v-model可以用来创建自定义的表单输入组件
<div id="app">
    <div>总数:{{total}}div>
    <abc v-model="total">abc>
    <br/>
    <button @click="down">-1button>
div>
<script>
    Vue.component('abc', {
        props:["value"],
        template: ``,
        methods:{
          update:function(e){
              this.$emit("input",e.target.value);
          }
        }
    });
    var app = new Vue({
        el: "#app",
        data: {
            total: 0
        },
        methods:{
            down:function(){
                this.total=this.total-1;
            }
        }
    })
script>

实现这样一个具有双向数据绑定的v-model组件要满足下面的两个要求:
1.子组件接受一个value属性。
2.子组件在有新的value时触发input事件。

参考:
1.Vue.js官方文档
2.《Vue.js实战》

你可能感兴趣的:(Vue.js,自定义事件,$emit,父链,子组件索引,自定义组件使用v-model)