vue组件通讯方式

          组件是 Vue.js 最强大的功能之一;组件可以扩展 HTML 元素,封装可重用的代码;而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。对于vue来说,组件之间的消息传递是非常重要的,下面是我对组件之间消息传递的常用方式的总结。

  • props 和 $emit  (常用)

  • $parent / $children 与 ref

  • provide 和 inject

  • $attrs 和 $listeners

  • $emit 和 $on  (非父子组件间通讯)

  • vuex状态管理

 

         1. props 和 $emit 

                父组件向子组件传递数据是通过props传递的,子组件传递数据给父组件是通过$emit触发事件,props 以单向数据流的形式可以很好的完成父子组件的通信。

               单向数据流:就是数据只能通过 props 由父组件流向子组件,而子组件并不能通过修改 props 传过来的数据修改父组件的相应状态。

               至于为什么这样做,Vue 官网做出了解释:  

                       

 1 // A页面 => 父组件页面
 2 <template>
 3   <div class='A'>
 4     A页面
 5     <B :message='message' @getChildData="getChildData"></B>
 6   </div>
 7 </template>
 8 <script>
 9 import B from './components/B.vue'
10 export default {
11   name: 'A',
12   data () {
13     return {
14       message: '来自A页面'
15     }
16   },
17  components: {
18     B
19   },
20   methods: {
21     //  执行子组件触发的事件
22     getChildData(val){
23         console.log(val)
24     }
25   }
26 }
27 </script>
28 
29 // B页面 => 子组件页面
30 <template>
31   <div class='A'>
32      B页面  
33      <button @click="handleClick(message)">{{message}}</button>
34   </div>
35 </template>
36 <script>
37 export default {
38   name: 'B',
39   data () {
40     return {
41 
42     }
43   },
44  props: {  
45 //   得到父组件传递过来的数据,这里的定义最好是写成数据校验的形式,免得得到的数据是我们意料之外的
46     message: {
47       type: String,
48       default: ''
49     }
50   },
51  methods: {
52     handleClick(val) { 
53          // 点击按钮传递数据给父组件
54          this.$emit("getChildData", val)
55     }
56   }
57 }
58 </script>

    1).父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件;

  2).子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件 

 

         2. $parent / $children 与 ref

             ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据。

             $parent/$children:  访问父 / 子实例

             注意:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据,(弊端:无法跨级或兄弟间通信)

             官方提示:需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

 1 // 父组件 => A页面
 2 <template>
 3   <div class='A'>
 4     A页面
 5     <B ref="comB"></B>
 6     <child />
 7   </div>
 8 </template>
 9 <script>
10 import B from './components/B.vue'
11 import child from './components/child.vue'
12 export default {
13   name: '',
14   data () {
15     return {
16        message: '来自A页面的message'
17     }
18   },
19   components: {
20     B,
21     child
22   },
23  mounted () {
24     //  通过ref给组件绑定名字comB,在父组件中,通过this.$refs.comB就可以访问了这个子组件了,包括访问子组件的data里面的数据,调用它的函数
25     console.log(this.$refs.comB.name) // 子组件页面
26     console.log(this.$refs.comB.getList()) // hello
27     // $children[1]获取的是第二个子组件,改变data里的childMessage值
28     const children = this.$children[1].childMessage = 'B页面的message'
29     console.log(children)  // B页面的message
30   }
31 }
32 </script>
33 
34 
35 // 子组件  => B页面
36 <template>
37   <div class='com'>
38     B页面
39     <C></C>
40   </div>
41 </template>
42 <script>
43 import C from './C.vue'
44 export default {
45   name: '',
46   data () {
47     return {
48       name: '子组件页面'
49     }
50   },
51   methods: {
52     getList () {
53       console.log('hello')
54       return 'hello'
55     }
56   }
57 }
58 </script>
59 
60 // 子组件 => child页面
61 <template>
62   <div class='child'>
63     {{childMessage}}
64   </div>
65 </template>
66 <script>
67 export default {
68   name: '',
69   data () {
70     return {
71       // 通过$parent获取父组件data中的message
72       childMessage: this.$parent.message
73     }
74   }
75 }
76 </script>

 

         3. provide 和 inject

             用于父组件向子孙组件传递数据,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效

             使用方法:provide在父组件中返回要传给下级的数据,inject在需要使用这个数据的子辈组件或者孙辈等下级组件中注入数据。

             需要注意的是:provide 和 inject 绑定并不是可响应的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的

 1 // A页面
 2   <template>
 3     <div class='A'>
 4       A页面
 5       <B ref="comB"></B>
 6       <child />
 7     </div>
 8   </template>
 9   <script>
10  import B from './components/B.vue'
11  export default {
12    name: '',
13    data () {
14      return {
15         
16      }
17    },
18    provide: { 
19      // 设置provide:name,它的作用将name这个变量提供给它的所有子组件
20      name: '来自A页面的name'
21    }
22  }
23  </script>
24  
25  // B页面
26  <template>
27    <div class='com'>
28      B页面 {{nameB}}
29      <C></C>
30    </div>
31  </template>
32  import C from './C.vue'
33  export default {
34    name: '',
35    data () {
36      return {
37         nameB:this.name
38      }
39    },
40    inject: ['name'], // 通过inject注入从A组件中提供的name变量
41  }
42  </script>
43  
44  // C组件
45  <template>
46    <div class='dom'>
47      C页面 {{nameC}}
48    </div>
49  </template>
50  <script>
51  export default {
52    name: '',
53    data () {
54      return {
55         nameC:this.name
56      }
57    },
58    inject: ['name'],// C组件在B组件里,B组件在A组件里,此时通过inject可以获取到从A组件中提供的name变量
59  }
60  </script>

            上面的代码可以看到,在A.vue里,我们设置了一个provide:name,值为来自A页面的name,它的作用就是将name这个变量提供给它的所有子组件,在B.vue中通过inject注入从A组件中提供的name变量,B组件中就可以通过this.name访问这个变量了,C组件是B组件的子组件,B组件是A组件的子组件,因此C组件通过inject注入从A组件提供的name变量,也可以通过this.name访问,这就是 provide / inject API 最核心的用法。

 

         4. $attrs 和 $listeners

           1. 一般用props方法传递,如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想直接传递数据给组件C就没办法, 只能是组件A通过 props 将数据传给组件B,然后组件B获取到组件A 传递过来的数据后再通过 props 将数据传给组件C。虽然能够实现,但是代码并不美观。无关组件中的逻辑业务增多了,代码维护也变得困难,再加上如果嵌套的层级越多逻辑也复杂,

           2. 案例1可以使用vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用

           针对上面问题Vue 2.4 提供了$attrs 和 $listeners 来实现能够直接让组件A传递消息给组件C。

           官方解释:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

           我的理解是:接收除了props声明外的所有绑定属性(class、style除外),通过this.$attrs

           

 1   // 父组件 A页面
 2   <template>
 3    <div class='aom'>
 4      A页面
 5       <B :name='name'  :message='message' :content='content' 
 6     @handleClicks='handleClicks'></B>
 7     </div>
 8   </template>
 9   
10   <script>
11  import B from './components/B.vue'
12  export default {
13    name: '',
14    data () {
15      return {
16        name: '来自A页面的name',
17        message: 'hello',
18        content: 'content'
19      }
20    },
21        methods: {
22            handleClicks (e) {
23                console.log(e)   //  接收C组件传递过来的参数{content: 'content'}
24            }
25        }
26  }
27  </script>
28  
29  // 子组件 B页面
30  <template>
31    <div class='bom'>
32      B页面
33      <C  v-bind="$attrs"  v-on="$listeners"></C>
34    </div>
35  </template>
36  
37  <script>
38  import C from './C.vue'
39  export default {
40    name: '',
41    data () {
42      return {
43 
44      }
45    },
46    inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性,不管inheritAttrs为true或者false,子组件中都能通过$attrs属性获取到父组件中传递过来的属性。
47    props: {
48     name: String  // name作为props属性绑定
49    },
50    components: {
51      C
52    },
53    created () {
54      console.log(this.$attrs)  // {message: 'hello', content: 'content'}
55    },
56  }
57  </script>
58  
59  // 孙组件 C页面
60  <template>
61    <div class='com'>
62      C页面
63          <div @click="handleClick">点击</div>
64    </div>
65  </template>
66  <script>
67  
68  export default {
69    name: '',
70    data () {
71      return {
72  
73      }
74    },
75    props: {
76      message: String  // message作为props属性绑定
77    },
78    created () {
79      console.log(this.$attrs)  //  { content: 'content'}
80    },
81        methods: {
82            // 通过点击事件传递给父组件
83            handleClick () {
84            this.$emit('handleClicks', this.$attrs)
85        }
86      }
87  }
88  </script>

            由于子组件在props中声明了name属性,$attrs中只有message和content两个属性,输出结果为:{message: 'hello', content: 'content'},

            在子组件上通过v-bind=“$attrs”,可以将属性继续向下传递,让孙组件也能访问到父组件的属性(接收除了props声明外的所有绑定属性(class、style除外)),这样传递多个属性会显得更加便捷,如果想要添加其他属性,可以继续绑定属性。

             此时我们又想到了一个问题,C组件(孙组件)的信息,怎么同步给A组件呢? 

             vue2.4版本新增了$listeners 属性,我们在B组件上 绑定 v-on=”$listeners”, 在A组件中,监听C组件触发的事件。就能把C组件发出的数据,传递给A组件。

 

         5. $emit 和 $on  (非父子组件间通讯)

             两个组件不是父子关系时可以使用此方法,EventBus 通过新建一个 Vue 事件 bus 对象,然后通过 bus.$emit 触发事件,bus.$on 监听触发的事件。

             const EventBus = new Vue(); // 相当于又new了一个vue实例,Event中含有vue的全部方法;

             EventBus.$emit('msg',this.msg); // 发送数据,第一个参数是发送数据的名称,接收时还用这个名字接收,第二个参数是这个数据现在的位置;

             EventBus.$on('msg',function(msg){ 接收数据,第一个参数是数据的名字,与发送时的名字对应,第二个参数是一个方法,要对数据的操作  })

             

 1 // event-bus.js 页面
 2 import Vue from 'vue'
 3 export const EventBus = new Vue()  // 创建一个事件中间件并将其导出
 4 
 5 // A页面
 6 <template>
 7   <div class='aom'>
 8     A页面
 9     <B></B>
10     <C></C>
11   </div>
12 </template>
13 <script>
14 import B from './components/B.vue'
15 import C from './components/C.vue'
16 export default {
17   name: '',
18   data () {
19     return {}
20   },
21  components: {
22     B,
23     C,
24   }
25 }
26 </script>
27 
28 // B页面
29 <template>
30   <div class='bom'>
31     B页面
32     <div @click="handleClick"> 点击</div>
33     <C></C>
34   </div>
35 </template>
36 <script>
37 import { EventBus } from './event-bus.js'
38 import C from './C.vue'
39 export default {
40   name: '',
41   data () {
42     return {
43       num: 1
44     }
45   },
46   methods: {
47     handleClick () {
48       // 传递数据方通过 EventBus.$emit(方法名,传递的数据)触发兄弟组件的事件
49       EventBus.$emit('addition', {
50         num: this.num++
51       })
52     }
53   }
54 }
55 </script>
56 
57 // C页面
58 <template>
59   <div class='com'>
60     C页面
61     <div>接收{{count}}</div>
62   </div>
63 </template>
64 <script>
65 import { EventBus } from './event-bus.js'
66 export default {
67   name: '',
68   data () {
69     return {
70       count: 0
71     }
72   },
73   mounted () {
74     // 接收数据方通过mounted钩子触发EventBus.$on()
75     EventBus.$on('addition', val => {
76       this.count = this.count + Number(val.num)
77     })
78   }
79 }
80 </script>

            移除事件的监听:

                 import { eventBus } from 'event-bus.js'

                EventBus.$off('addition', {})

 

         6. vuex状态管理

             Vuex 是状态管理工具,实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。Action用于异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

              1.   state:用于数据的存储,是store中的唯一数据源

              2.  getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算

              3.  mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件

              4.  actions:类似mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作

              5.  modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护详细的关于Vuex的介绍,可去查看官网文档 

 

 

               

 

posted @ 2021-09-09 17:23  Naynehcs  阅读(53)  评论(0编辑  收藏  举报