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的介绍,可去查看官网文档