# Vue中组件通信的九种方法
# 前言
Vue中的组件通信是一个比较复杂的一个知识点,涵盖各种api和语法糖,接下来我们分别来介绍下常见的九种组件通信方法
# props 和 $emit
这是我们日常开发最常见的一种父子组件通信的方法了,我们来看一个简单的例子,后文全部方法都围绕这个例子进行
// App.vue
<Parent />
import Parent from './components/Parent'
export default {
components: {
Parent,
}
}
// Parent.vue
<Son @change="change" :number="number" />
import Son from './Son'
export default {
data() {
return {
number: 100,
}
},
methods: {
change(newVale) {
this.number = newVale
}
}
}
// Son.vue
<div>子组件
{{ number }}
<button @click="change">修改父组件的值</button>
</div>
export default {
props: {
number: {
type: Number
}
},
methods: {
change() {
this.$emit('change', 200)
}
}
}
我们可以发现,父组件通过props
将数据传递给子组件,而子组件可以通过$emit
的方式调用父组件的方法修改父组件的值,我们知道Vue和React都是单向数据流,也就是数据是从上往下传递的,当我们子组件修改父组件的数据的时候,自己拿到的数据也会随着修改
# $parent 和 $children
介绍了最常见的一种用法,我们接下来看下一种更暴力的用法 $parent
和 $children
// Son.vue
<button @click="change">修改父组件的值</button>
methods: {
change() {
this.$parent.change(200)
}
}
我们可以通过$parent
拿到父组件的实例,然后调用父组件的方法改变父组件的数据,同样道理我们在父组件中也可以使用$children
的方法修改子组件的值,这里就不演示啦
# $dispatch
这个也是在element ui 的源码中看到的一个方法
// main.js
Vue.prototype.$dispatch = function(eventName, newValue) {
let parent = this.$parent
while (parent) {
parent.$emit(eventName, newValue)
parent = parent.$parent // 继续递归接着往上找
}
}
我们将$dispatch
挂载到Vue的原型上方便调用,我们可以看到,$dispatch
的原理是一直递归找父组件,然后执行父组件中对应的$emit
方法,我们来看下怎么用
// Parent.vue
<div>父组件
<Son @change="change" :number="number" />
</div>
export default {
data() {
return {
number: 100,
}
},
methods: {
change(newVale) {
this.number = newVale
}
}
}
// Son.vue
<div>子组件
{{ number }}
<br/>
<Grandson />
</div>
export default {
props: {
number: {
type: Number
}
},
}
// Grandson.vue
<div>孙组件
<button @click="change">修改父组件的值</button>
</div>
export default {
methods: {
change(newVale) {
this.$dispatch('change', 200)
}
}
}
我们可以发现在孙子组件中调用$dispatch
方法,他会触发所以父组件中的$emit('xxx', xxx)
方法,达到修改对应父组件数据的目的
# $broadcast
$broadcast
的原理和$dispatch
很像,$dispatch
是一直往父元素上面找,而$broadcast
刚刚相反,一直往子元素上面找对应的方法执行
// main.js
Vue.prototype.$broadcast = function(eventName, newValue) {
let children = this.$children
function broad(children) {
children.forEach(child => {
child.$emit(eventName, newValue)
if (child.$children) {
broad(child.$children)
}
})
}
broad(children)
}
同样是挂载原型的方法,有了这个方法我们就可以在父组件中主动调用全部后代组件中的$emit
方法了
# .sync 和 v-modal
# .sync
接下来我们看到的这种用法其实是 props
和 $emit
的一些语法糖, 我们来看下使用props
和 $emit
时候的代码
// Parent.vue
<Son @update:number="newValue =>number = newValue" :number="number" />
// Son.vue
<button @click="change">更新父组件的方法</button>
methods: {
change() {
this.$emit('update:number', 200)
}
}
我们只需要将$emit(fn)
中的fn改为update:xxx
就可以了,其中xxx为父组件传递给子组件的数据名
为什么要这样写呢?因为上述代码Vue给我们提供了一个语法糖
// Parent.vue
<Son @update:number="newValue =>number = newValue" :number="number" />
// 等价于
<Son :number.sync="number" />
不过需要注意的是,在子组件中调用$emit(fn, val)
的时候fn的名字一定是这样的格式的update:xxx
# v-modal
既然 props
和 $emit
可以这么玩了,那我们再试一下另外的玩法
// Parent.vue
<Son @input="newValue =>number = newValue" :value="number" />
// Son.vue
props: {
value: {
type: Number
}
},
change() {
this.$emit('input', 200)
}
我们只需要稍微的修改一下props
和 $emit
的用法也可以实现相应的逻辑,但是这样写的话我们不由想到这种写法是的语法糖是
v-modal
,那我们的父组件可以这样使用
<Son v-model="number" />
但是啊v-modal
的局限性是只能传递value
, 而且只可以传递一次
# $attrs 和 $listeners
# $attrs 和 v-bind
假设我们有一个需求,要将父组件的数据传递给孙组件,但是我们的子组件又没有使用到这些数据,这个时候我们可以使用$attrs
了
// Parent.vue
<Son :number="number" :count="count" />
// Son.vue
<Grandson v-bind="$attrs" />
// Grandson.vue
{{ $attrs }}
在子组件中可以通过v-bind="$attrs"
的方式将父组件传递下来的数据原封不动的传递给孙组件,而孙组件可以通过$attrs
来接收,不过这个用法有个局限性就是子组件不可以接收父组件的数据,也就是不可以有props
钩子
# $listeners 和 v-on
既然可以这样传数据了,那方法是不是也可以这么玩,答案是肯定的
// Parent.vue
<Son :number="number" @change="change" @say="say" :count="count" />
// Son.vue
<Grandson v-on="$listeners" />
// Grandson.vue
mounted() {
console.log(this.$listeners)
}
通过 $listeners
和 v-on
的配合可以将全部事件传递给孙组件,但是这里需要注意的一点是,子组件也是可以通过$listeners
来接收父组件传递的全部事件的
# eventbus
我们先来说下eventbus
的原理,假设现在2个组件A和B需要通信,那么我可以在组件C上面用$on
发布一个事件,那么A和B就可以通过C.$emit()
来互相通信了吧,就是弄一个全局的发布订阅
// main.js
Vue.prototype.$bug = function() {
return new Vue()
}
我们知道Vue本身就是具有发布订阅能力的,这里我们直接new Vue()
就好啦
那么怎么使用呢?
// 组件A - 在组件A中发布事件
this.$bus.$on('change', ()=>{})
// 组件B - 在组件B中可以订阅这个事件实现通信
this.$bus.$emit('change')
# ref
其实一直在纠结ref
算不算组件通信的一种,但是我们可以通过ref
拿到某一个组件的实例,也就是拿到某一个组件的方法和数据
// Parent.vue
<Son ref="son"/>
mounted() {
console.log(this.$refs.son)
}
可以通过$refs
拿到某个组件的实例
# provide 和 inject
接下来我们来说一下很多人都不知道的一种组件传值方法provide
和 inject
我们只需要在父组件使用provide
定义需要向下传递的数据,不管层级多深后代组件都可以通过inject
接收
// Parent.vue
export default {
provide() {
return {vm: this}
},
}
父组件向下传递数据vm
// Son.vue
export default {
inject: ['vm'],
mounted() {
console.log(this.vm)
}
}
子组件可以通过inject
接收
但是这个方法在日常开发中不建议使用,因为太乱太难管理了
# 总结
Vue的组件通信太复杂了,因为语法糖和api太多了,所以我们还是选择vuex