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>
View Code

食品列表页面包含食品列表,购物车组件(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>
View Code

食品详情页面包含"加入购物车"按钮和数量控制组件(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>
View Code

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>
View Code

 

问题:

当点击食品列表页(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

 

posted @ 2019-08-23 16:38  smartsmile  阅读(387)  评论(0编辑  收藏  举报