>[success] # 组件通信(三) ~~~ 1.了解'$on'与'$emit' 2.根据Vue.js 1.x 的 '$dispatch' 与 '$broadcast' 两方法思路伪造一个, 属于vue.js 2.x 系列同样的方法.其中 '$dispatch'子传父或者孙子传爷爷, '$broadcast' 则相反,但是只会找到最近的触发 ~~~ >[info] ## 了解 '$on'与'$emit' ~~~ 1.使用 $on(eventName) 监听事件第二个参数是个方法, 使用 $emit(eventName) 触发事件第二个参数是传值, 这两个方法常见的使用场景就是'bus' 2.要注意 '$on' 监听需要在 mounted 或 created 钩子中来监听。 3.可以参考笔者另一个笔记内容'前端知识 -- 学习整理',这个思路就是设计 模式中的'发布订阅'模式 ~~~ >[danger] ##### 不是只有bus 才能用 ~~~ 1.$on 监听了自己触发的自定义事件 test,因为有时不确定何时会触发事件, 一般会在 mounted 或 created 钩子中来监听。 ~~~ * 自己触发自己的$emit 和 $on,下面写法多此一举建议直接handleEmitEvent触发alter ~~~ <template> <div> <button @click="handleEmitEvent">触发自定义事件</button> </div> </template> <script> export default { methods: { handleEmitEvent () { // 在当前组件上触发自定义事件 test,并传值 this.$emit('test', 'Hello Vue.js') } }, mounted () { // 监听自定义事件 test this.$on('test', (text) => { window.alert(text); }); } } </script> ~~~ * 运行效果 ![](https://box.kancloud.cn/e0e510d7ef4262335aca864d09748cc2_649x190.png) >[info] ## 自定义 dispatch 和 broadcast 方法 思路 ~~~ 1.在vue1.x 中'$dispatch' 是子孙组件向父爷组件传递,'broadcast '则相反, 但是只会找到最近的触发 2.两者都是使用'$on' 触发 ~~~ >[danger] ##### 定义前看一下vue1.x版本使用 ~~~ 1.在这里 还要强调说明监听的'$on'要在 mounted 或 created 钩子中来监听。 ~~~ * 在子组件定义 ~~~ <!-- 注意:该示例为 Vue.js 1.x 版本 --> <!-- 子组件 --> <template> <button @click="handleDispatch">派发事件</button> </template> <script> export default { methods: { handleDispatch () { this.$dispatch('test', 'Hello, Vue.js'); } } } </script> ~~~ * 在父组件中使用 ~~~ <!-- 父组件,部分代码省略 --> <template> <child-component></child-component> </template> <script> export default { mounted () { this.$on('test', (text) => { console.log(text); // Hello, Vue.js }); } } </script> ~~~ >[danger] ##### 封装思路 ~~~ 1.通过上面1.x案例可以看出来,大体上需要两个参数一个是触发的事件名字,一个是传 递的参数,但是我们自定义的需要使用一个标记参数,来找到我们需要匹配的组件,如何 找这里就利用创建组件的使用name属性如下图 ~~~ ![](https://box.kancloud.cn/07b1a1d66d13d37193f498972742ea8a_539x151.png) >[info] ## 正式封装 ~~~ 1.思路其实就是利用 '$on' 和 '$emit' 配合,通过我们自己写的这个方法,让对应 要调用的父组件或者子组件,this指向他本身,这样形成给其自身加入对应的方法, 但可以有子组件或父组件的传值 2.首先明确一点'$emit' 和 '$on' 是典型的发布的订阅模式,'$on'通过订阅的方式,来接受'$emit' 发布的内容,当然他的发布和订阅 必须是同一个'this',因此利用vue自带的'$parent' 和'$children' 从父组件开始找也好,从子组件开始找也好,原理就是层级向上找让彼此可以发布订阅在一起 ~~~ 代码参考[iView](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fiview%2Fiview%2Fblob%2F2.0%2Fsrc%2Fmixins%2Femitter.js)。 >[danger] ##### 从子组件传递给父组件甚至爷爷组件 ~~~ 1.获取当前组件的'parent',因为刚才构想过,我们需要传递三个参数,其中一个参数要匹配 的目标组件名字,因此逻辑就是去找'parent' 和要匹配的名字是否一致,找到符合的'parent' 给这个父组件添加'$emit'事件,在这个父组件中使用'$on' 去触发 ~~~ ~~~ // 子传父,孙子传爷爷或者爸爸 /* * @params:componentName 对应组件名 ,eventName 事件名称,params 函数参数 * */ dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; console.log(this.$options) console.log(this.$options.name) // 递归一层一层找 while (parent && (!name || name !== componentName)) { parent = parent.$parent; // 有父组件名称 在去找组件name,如果没有了 就不用特意去找 if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit(parent, [eventName].concat(params)); // parent.$emit(eventName,params); } }, ~~~ >[danger] ##### 从爷爷组件给孙子组件,父传子 ~~~ 1.思路就找每一个children,找到是否 有和名字匹配的并且绑上$emit()事件 ~~~ ~~~ function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } }); } ~~~ >[danger] ##### 将所有代码放到mixins进行存储使用(最后的封装) ~~~ function broadcast(componentName, eventName, params) { // 当前的this 指向是在methods broadcast 中被改变了 // 因此现在的this 是每一个组件的this this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { // 递归 更改新的this,获取新的组件中的children,也就是更新成当前父组件的this broadcast.apply(child, [componentName, eventName].concat([params])); } }); } export default { methods: { // 子传父,孙子传爷爷或者爸爸 /* * @params:componentName 对应组件名 ,eventName 事件名称,params 函数参数 * */ dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; console.log(this.$options) console.log(this.$options.name) while (parent && (!name || name !== componentName)) { parent = parent.$parent; // 有父组件名称 在去找组件name,如果没有了 就不用特意去找 if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); // parent.$emit(eventName,params); } }, broadcast(componentName, eventName, params) { // 当前this 也就是其中的父组件 有了broadcast方法 broadcast.call(this, componentName, eventName, params); } } }; ~~~ >[danger] ##### 使用 ~~~ 1.要注意'$on' 要放到mounted 或 created 去使用 ~~~ * 父组件 ~~~ <!-- A.vue --> <template> <div> <button @click="handleClick">触发事件</button> <my-button class="aaa" id="222" index="666"></my-button> </div> </template> <script> import Emitter from '../mixins/emitter.js'; import MyButton from '@/components/MyButton' export default { name: 'componentA', mixins: [ Emitter ], methods: { handleClick () { // 父传子 this.broadcast('componentB', 'on-message', 'Hello Vue.js'); }, }, created(){ // 子传父接受 this.$on('test', (val)=>{ console.log(val) }) }, components:{ MyButton } } </script> ~~~ * 子组件 ~~~ <template> <div index="987"> <button @click="clickToPranent">点击</button> </div> </template> <script> import Emitter from '@/mixins/emitter'; export default { inheritAttrs: false, name: 'componentB', // 使用封装 mixins: [ Emitter ], created () { // 接收父传子 this.$on('on-message', this.showMessage); }, methods: { showMessage (text) { console.log(1) window.alert(text); }, clickToPranent(){ // 调用子传父 this.dispatch('componentA','test','aaa') }, } } </script> <style scoped> </style> ~~~