Vue——组件通讯
1.自定义事件
组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信。
从父组件像向子组件通信,通过props传递数据就可以了。
当子组件需要向父组件传递数据时,就要用到自定义事件。
v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。
子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> </head> <body> <div id="app"> <p>总数: {{ total }}</p> <!--定义两个自定义事件--> <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component> <!--两个自定义事件向一个函数传参,这样能保证counter一致--> </div> <script> Vue.component('my-component', { template: '' + '<div>' + '<button @click="handleIncrease">+1</button>' + '<button @click="handleReduce">-1</button>' + '</div>', // 组件绑定两个方法,这两个方法肯定是需要的 data: function () { return { counter: 0 } }, methods: { // 这两个方法都操控的同一个counter,而且现在是在组件里面,还需要向父组件传递数据 // 当子组件需要向父组件传递数据的时候,就要用到自定义事件,v-on除了可以监听事件之外,还可以用于组件之间的自定义事件 // 触发事件,将消息传递给父组件 //$emit()第一个参数是自定义事件的名称,第二个参数是向自定义事件传递的参数 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> </body> </html>
2.使用v-model
还有另外一个更加快捷的方法,使用v-model做双向的数据绑定。
在自定义的标签里面有counter这个属性,使用v-model直接绑定total这个属性。
不过这里面使用的是input这个特殊事件名。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> <p>{{ total }}</p> <!--我们还可以在自定义组件上使用v-model--> <!--这样将total直接与组件绑定到i此绑定在一次--> <my-component v-model="total"></my-component> </div> <script> Vue.component('my-component',{ template: '<button @click="handleIncrease">+1</button>', data: function () { return { counter: 0 } }, methods: { handleIncrease: function () { this.counter++; //这次使用的是特殊的input this.$emit('input',this.counter) } } }); var app = new Vue({ el: "#app", data: { total: 0, } }) </script> </body> </html>
v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> <p>总数: {{ total }}</p> <my-component v-model="total"></my-component> <!--直接绑定到total上面--> <button @click="handleReduce">-1</button> <!--这其实是一个三方通信--> <!--v-modelp->my-component--> <!--my-component->button--> </div> <script> Vue.component('my-component',{ props:['value'], template: '<input :value="value" @input="updateValue">', //input标签绑定到这个函数,组件中接收value这个值 methods: { updateValue: function (event) { //event代表当前操作的这个对象 this.$emit('input',event.target.value); //将值传回去 } } }); var app = new Vue({ el: "#app", data: { total: 0, }, methods: { handleReduce: function () { this.total--; } } }) </script> </body> </html>
实现这样一个具有双向绑定的v-model组件要满足下面两个要求:
接收一个value属性
在有新的value时触发input事件
3.非父子组件通信
在vue1中,$dispatch用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在vue实例的event选项内接收,
还提供了$broadcast用于上级向下级广播事件。
这两种方法一旦发出事件之后,任何组件都可以接收,就近原则而且会在第一次接收到后停止冒泡,除非返回true。
在vue2中这两个事件被废弃,因为基于组件树结构的事件流的方式难以让人理解,并且在扩展的过程中会变得越来越脆弱。
并且不能解决兄弟组件之间的通信问题。
在vue2中使用一个空的Vue实例作为中央事件总线,也就是一个中介,专门用来传达消息。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> {{ message }} <my-component></my-component> </div> <script> var bus = new Vue(); //创建一个空的Vue实例,里面并没有任何内容 Vue.component('my-component',{ //定义组建 template: '<button @click="handleEvent">传递事件</button>', methods: { handleEvent: function () { bus.$emit('on-message','来自组件my-component的内容') //将事件发送到bus } } }); var app = new Vue({ el: "#app", data: { message: '', }, mounted: function () { //在初始化app的过程中,监听来自bus的消息 var _this = this; bus.$on('on-message',function (msg) { //在初始化的过程中,监听来自bus实例的事件 _this.message = msg }) } }) </script> </body> </html>
首先创建一个名为bus的空Vue实例,里面没有任何内容。
然后全局定义组件my-component,最后创建Vue实例app,在app初始化时,
也就是生命周期钩子函数里面监听来自bus的事件on-message,
而在组件my-component中点击按钮会通过bus把事件on-message发出去,
此时app就会就收来自bus的事件,进而在回调里完成自己的业务逻辑。
这种方法巧妙的轻量的实现了任何组件之间的通讯,包框父子\兄弟\跨级,
如果深入使用,可以扩展bus实例,给他添加data、methods、computed等选项,
在业务中,经常需要共享一下信息,比如用户登录星系、授权token,只需要让bus获取一次机型了。
4.父链
在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,
父组件也可以通过this.$children访问他的所有子组件,而且可以递归向上或向下无限访问。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> {{ message }} <my-component></my-component> </div> <script> Vue.component('my-component',{ template: "<button @click='handleChange'>通过父链直接修改数据</button>", data: function () { return { message: '来自组件的消息' } }, methods: { handleChange: function () { this.$parent.message = this.message } } }); var app = new Vue({ el: "#app", data: { message: '老子的老大' } }) </script> </body> </html>
尽管Vue允许这样做,但是并不建议这样做,
子组件不应该过多的依赖父组件,更不应该主动去修改它的数据。
5.子组件索引
当子组件比较多的时候,通过this.$children来遍历组件比较麻烦。
vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> <p>{{ preMessage }}</p> <button @click="handleRef">通过ref过去子组件实例</button> <my-component ref="comA"></my-component> <!--指定索引标签--> </div> <script> Vue.component('my-component',{ template: "<div></div>", data: function () { return { message: '子组件的内容' } } }); var app = new Vue({ el: "#app", data: { preMessage: '父组件内容' }, methods: { handleRef: function () { this.preMessage = this.$refs.comA.message //访问指定的子组件 } } }) </script> </body> </html>
效果: