Vue父子组件和非父子组件间的通信
在学习幕课网"饿了么"程序时,遇到了一个组件通信的问题:
在food详情页面只能通过点击“购物”
1.goods.vue(食品列表组件),2.food.vue(食品详情组件),3.cartcontrol.vue(数量控制组件),4.shopcart.vue(购物车组件)
1.goods.vue组件内容:
1 <template> 2 <div class="goods"> 3 <cartcontrol @cart-add-cartcontrol="addCart" :food="food"></cartcontrol> 4 <shopcart ref="shopcart" :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart> 5 <food ref="food" :food="selectedFood" @cart-add-food="addCart"></food> 6 </div> 7 </template> 8 <script type="text/ecmascript-6"> 9 import cartcontrol from "./cartcontrol"; 10 import shopcart from "./shopcart"; 11 import food from "./food"; 12 export default { 13 components: { 14 shopcart, 15 cartcontrol, 16 food 17 }, 18 methods: { 19 addCart(target) { 20 // 通过refs调用shopcart中的drop方法 21 // 体验优化,异步执行下落动画 22 this.$nextTick(() => { 23 this.$refs.shopcart.drop(target); 24 }); 25 }, 26 } 27 } 28 </script> 29 <style lang="stylus" rel="stylesheet/stylus"> 30 </style>
食品列表页面包含食品列表,购物车组件(shopcart.vue),每个食品(food.vue)后都有数量控制组件(cartcontrol.vue)
2.food.vue组件的内容:
1 <template> 2 <div class="food"> 3 <cartcontrol :food="food"></cartcontrol> 4 <transition name="fade"> 5 <div class="buy" v-show="!food.count || food.count===0" @click.stop.prevent="addFirst($event)">加入购物车</div> 6 </transition> 7 </div> 8 9 </template> 10 <script type="text/ecmascript-6"> 11 import cartcontrol from './cartcontrol'; 12 export default { 13 components: { 14 cartcontrol 15 } 16 methods: { 17 addFirst(event) { 18 // console.log(event.target); 19 // $emit调用的自定义事件v-on:cart-add(或者:cart-add) 20 // this.$parent._drop(event.target); 21 this.$emit("cart-add-food", event.target); 22 Vue.set(this.food, "count", 1); 23 } 24 } 25 } 26 </script> 27 <style lang="stylus" rel="stylesheet/stylus"> 28 </style>
食品详情页面包含"加入购物车"按钮和数量控制组件(cartcontrol.vue)
3.cartcontrol.vue组件内容:
1 <template> 2 <div class="cartcontrol"> 3 <div class="cart-add icon-add_circle" @click.stop.prevent="addCart($event)">数量控制组件</div> 4 </div> 5 </template> 6 <script type="text/ecmascript-6"> 7 import Vue from 'vue'; 8 export default { 9 props: { 10 food: { 11 type: Object 12 } 13 }, 14 methods:{ 15 //增加event是为了阻止PC端点击会出现两次增加,实际我这里没有出现该种情况 16 addCart(event) { 17 // console.log('调用到cartcontrol.vue中的addCart方法并分发cartAdd'); 18 this.$emit('cart-add-cartcontrol', event.target); 19 } 20 } 21 } 22 </script>
4.shopcart.vue组件内容:
1 <template> 2 <div class="shopcart"> 3 <cartcontrol :food="food"></cartcontrol> 4 </div> 5 </template> 6 <script type="text/ecmascript-6"> 7 import cartcontrol from './cartcontrol' 8 export default { 9 components: { 10 cartcontrol 11 }, 12 methods: { 13 drop (el) { 14 console.log("我是小球下落方法"); 15 } 16 } 17 }; 18 </script>
问题:
当点击食品列表页(goods.vue)中的“+”按钮时会出现小球进入购物车的效果:
实现过程:
1.cartcontrol.vue中@click.stop.prevent="addCart($event)"调用addCart($event)并传入$event
2.addCart()中this.$emit('cart-add-cartcontrol', event.target);通过@cart-add-cartcontrol="addCart"触发父组件goods.vue中addCart(target)方法并传入target
3.addCart(target)中this.$refs.shopcart.drop(target);通过ref="food"调用shopcart.vue中的drop(target)方法并传入target
4.最终实现小球从“+”按钮滚落到购物车的效果
同理:在食品详情页面中点击“加入购物车”按钮时会出现小球进入购物车的效果
实现过程:
1.food.vue中@click.stop.prevent="addFirst($event)"调用addFirst($event)并传入$event
2.addFirst()中this.$emit('cart-add-food', event.target);通过@cart-add-food="addCart"触发父组件goods.vue中addCart(target)方法并传入target
3.addCart(target)中this.$refs.shopcart.drop(target);通过ref="food"调用shopcart.vue中的drop(target)方法并传入target
4.最终实现小球从“+”按钮滚落到购物车的效果
但是
1.在食品详情页面food.vue的“+”按钮无法通过cartcontrol.vue中this.$emit调用祖父组件addCart方法(包含关系:goods.vue包含shopcart.vue,food.vue包含cartcontrol.vue)
2.购物车shopcart.vue中的“+”无法无法通过cartcontrol.vue中this.$emit调用祖父组件addCart方法(包含关系:goods.vue包含shopcart.vue,shopcart.vue包含cartcontrol.vue)
当然也可以通过方法传递,但是那样太麻烦了,需要在cartcontrol.vue中区分不同的操作,那么可以通过$root.eventhub来实现这样的效果:
第一步,在vue2.5中在初始化根Vue之前,给data添加一个 名字为eventhub 的空vue对象(main.js文件中)
new Vue({ el: '#app', router, data: { eventhub: new Vue() }, components: { App }, template: '<App/>' })
第二步:在各组件中使用vue的实例属性$root来访问我们当前组件树的根 Vue 实例,并使用vm.$root.eventHub来访问我们定义的事件发射器eventHub
修改cartcontrol.vue中的addCart(event)方法
addCart(event) { // console.log('调用到cartcontrol.vue中的addCart方法并分发cartAdd'); // this.$emit('cart-add-cartcontrol', event.target); this.$root.eventHub.$emit('cart.add', event.target); // 传输点击的目标元素 }
修改food.vue中的addFirst(event)方法
addFirst(event) { // console.log(event.target); // $emit调用的自定义事件v-on:cart-add(或者:cart-add) // this.$parent._drop(event.target); this.$root.eventHub.$emit('cart.add', event.target); // 传输点击的目标元素 Vue.set(this.food, "count", 1); }
在shopcart.vue中添加监听方法 :
created() { // 获取按钮组件的点击的元素,用在drop方法里 this.$root.eventHub.$on('cart.add', this.drop); }, beforeDestroy() { this.$root.eventHub.$off('cart.add', this.drop); }
这样之后,非父子组件的通信就完成了,官方对于非父子组件通信也推荐了一个专用的状态管理层:Vuex。