vue组件通讯7种方式
props/@on+$emit
用于实现父子组件间通信。通过 props
可以把父组件的消息传递给子组件:
// parent.vue <child :title="title"></child>
// child.vue props: { title: { type: String, default: '', } }
这样一来 this.title
就直接拿到从父组件中传过来的 title
的值了。注意,你不应该在子组件内部直接改变 prop
,这里就不多赘述,可以直接看官网介绍。
而通过 @on+$emit
组合可以实现子组件给父组件传递信息:
// parent.vue <child @changeTitle="changeTitle"></child>
// child.vue this.$emit('changeTitle', 'bubuzou.com')
attr和listeners
Vue_2.4
中新增的 $attrs/$listeners
可以进行跨级的组件通信。$attrs
包含了父级作用域中不作为 prop
的属性绑定(class
和 style
除外),好像听起来有些不好理解?没事,看下代码就知道是什么意思了:
// 父组件 index.vue <list class="list-box" title="标题" desc="描述" :list="list"></list>
// 子组件 list.vue props: { list: [], }, mounted() { console.log(this.$attrs) // {title: "标题", desc: "描述"} }
在上面的父组件 index.vue
中我们给子组件 list.vue
传递了4个参数,但是在子组件内部 props
里只定义了一个 list
,那么此时 this.$attrs
的值是什么呢?首先要去除 props
中已经绑定了的,然后再去除 class
和 style
,最后剩下 title
和 desc
结果和打印的是一致的。基于上面代码的基础上,我们在给 list.vue
中加一个子组件:
// 子组件 list.vue <detail v-bind="$attrs"></detial>
// 孙子组件 detail.vue // 不定义props,直接打印 $attrs mounted() { console.log(this.$attrs) // {title: "标题", desc: "描述"} }
在子组件中我们定义了一个 v-bind="$attrs"
可以把父级传过来的参数,去除 props
、class
和 style
之后剩下的继续往下级传递,这样就实现了跨级的组件通信。
$attrs
是可以进行跨级的参数传递,实现父到子的通信;同样的,通过 $listeners
用类似的操作方式可以进行跨级的事件传递,实现子到父的通信。$listeners
包含了父作用域中不含 .native
修饰的 v-on
事件监听器,通过 v-on="$listeners"
传递到子组件内部。
// 父组件 index.vue <list @change="change" @update.native="update"></list> // 子组件 list.vue <detail v-on="$listeners"></detail>
// 孙子组件 detail.vue mounted() { this.$listeners.change() this.$listeners.update() // TypeError: this.$listeners.update is not a function }
provide/inject组合拳
provide/inject
组合以允许一个祖先组件向其所有子孙后代注入一个依赖,可以注入属性和方法,从而实现跨级父子组件通信。在开发高阶组件和组件库的时候尤其好用。
// 父组件 index.vue data() { return { title: 'bubuzou.com', } } provide() { return { detail: { title: this.title, change: (val) => { console.log( val ) } } } } // 孙子组件 detail.vue inject: ['detail'], mounted() { console.log(this.detail.title) // bubuzou.com this.detail.title = 'hello world' // 虽然值被改变了,但是父组件中 title 并不会重新渲染 this.detail.change('改变后的值') // 执行这句后将打印:改变后的值 }
provide
和 inject
的绑定对于原始类型来说并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。这也就是为什么在孙子组件中改变了 title
,但是父组件不会重新渲染的原因。
EventBus
以上三种方式都是只能从父到子方向或者子到父方向进行组件的通信,而我就比较牛逼了😀,我还能进行兄弟组件之间的通信,甚至任意2个组件间通信。利用 Vue
实例实现一个 EventBus
进行信息的发布和订阅,可以实现在任意2个组件之间通信。有两种写法都可以初始化一个 eventBus
对象:
通过导出一个 Vue
实例,然后再需要的地方引入:
// eventBus.js import Vue from 'vue' export const EventBus = new Vue()
1.使用 EventBus
订阅和发布消息:
import {EventBus} from '../utils/eventBus.js' // 订阅处 EventBus.$on('update', val => {}) // 发布处 EventBus.$emit('update', '更新信息')
2.在 main.js
中初始化一个全局的事件总线:
// main.js Vue.prototype.$eventBus = new Vue()
使用:
// 需要订阅的地方 this.$eventBus.$on('update', val => {}) // 需要发布信息的地方 this.$eventBus.$emit('update', '更新信息')
如果想要移除事件监听,可以这样来:
this.$eventBus.$off('update', {})
上面介绍了两种写法,推荐使用第二种全局定义的方式,可以避免在多处导入 EventBus
对象。这种组件通信方式只要订阅和发布的顺序得当,且事件名称保持唯一性,理论上可以在任何 2 个组件之间进行通信,相当的强大。但是方法虽好,可不要滥用,建议只用于简单、少量业务的项目中,如果在一个大型繁杂的项目中无休止的使用该方法,将会导致项目难以维护。
vuex
vuex太多了,就请自行查看vuex官网了