Vue组件交互之bus模式
目录一、bus模式简介一、bus模式简介bus模式是一种借助事件实现任意两组件交互的解决方案。通常来说,在不借助bus模式的情况下,事件的触发只会发生在父子组件之间,并且只能由子组件向父组件触发事件。它的大致实现模式如下:parent.vue<template><child@tick="handleTick"></child></template>&
一、bus模式简介
1. bus简例
bus是一种通过事件实现组件交互的通信模式,它借助一个额外的Vue实例作为事件管理中心。任何引入了该Vue实例的组件都处于同一个事件环路内,可以相互注册和触发事件。一个简单的bus实例如下:
bus.js
import Vue from 'vue';
const bus = new Vue();
export default bus;
component1.vue
...
import bus from './bus.js';
bus.$on('move', (payload) => {
...
})
component2.vue
import bus from './bus.js';
...
bus.$emit('move', payload);
这里bus是一个空Vue实例,我们在component1和component2中均引入了这个实例,并在component1中注册了对move
事件的监听。接下来我们在component2的合适时机下触发move
事件,这样component1就可以接收到这个事件,并触发对应的回调。
如果bus.$on
注册在组件内部(如methods或各个生命周期函数内),由于箭头函数内部没有this,因此你可以在回调函数内直接通过this访问当前组件实例(如果回调函数是常规函数,可以将组件实例保存在一个局部变量内)。
那么为什么需要这样一个模式呢?
2. 为什么需要bus?
这是因为,通常来说,在不借助bus模式的情况下,事件的触发只会发生在父子组件之间,并且只能由子组件向父组件触发事件。它的大致实现模式如下:
parent.vue
<template>
<child
@tick="handleTick"
></child>
</template>
<script>
export default {
methods: {
handleTick () { ... }
}
}
</script>
child.vue
...
this.$emit('tick');
...
父组件向子组件绑定了一个对tick
事件的监听,并定义了处理函数,而子函数可以在恰当的时机向父组件触发该事件。这种模式在封装第三方组件时极其常用。
另一种不太常见的交互是,由父组件直接调用子组件内的方法:
parent.vue
<template>
<child ref="child"></child>
</template>
<script>
export default {
mounted () {
this.$refs.child.tick();
}
}
</script>
这里父组件通过ref
属性拿到了对子组件的引用,然后直接调用了子组件内定义的tick
方法。当然,真正的函数调用仍然发生在子组件内。这种交互不涉及事件。
上述两种方法都只能作用于父子组件之间,对于无直接父子关系的组件则束手无策,这也是bus模式的使用背景。
二、bus的应用
1. bus原理
上文的例子中只展示了bus模式的简单用法,在介绍其他用法之前,我们先通过一张图来理解何为bus模式:
由于bus是一个完整的Vue实例,因此它具备事件管理能力。我们在任意组件内导入bus,并通过bus.$on
注册一个事件监听时,该回调函数就会进入bus内的事件队列。任何引入了bus的组件,都可以订阅任何感兴趣的事件(即注册回调事件)。
事件的触发是同样的道理,我们只需要在组件内引入bus,然后通过bus.$emit
触发一个事件即可。触发了一个事件后,事件队列中的所有回调函数都会按照注册顺序依次执行。
需要注意的是,回调函数的注册和执行其实都是发生在bus实例上。因此,如果所传入的回调函数是普通函数,那么函数内的this将指向bus实例,而不是注册事件的那个组件实例:
...
bus.$on('move', function(pos){
this.pos = pos;
})
使用箭头函数时不会有这个问题,它内部的this指向当前组件实例:
let temp = 123;
bus.$on('move', pos => {
this.pos = pos;
console.log(temp);
})
我在这里特意定义了一个变量temp来说明问题。我们知道,通过作用域链,函数内可以访问外部的变量temp。同样的,由于箭头函数不会重新定义this,因此函数内的变量this可以通过作用域链拿到外部this。
2. bus的使用
理解了bus模式的原理后,它的使用就非常简单了。任何需要共享某些事件的组件只需引入同一个bus,就可以注册和触发共享的事件:
component1.vue
<template>
<button @click="handleClick">点击我</button>
</template>
<script>
import bus from './bus.js';
export default {
methods: {
handleClick () {
bus.$emit('click');
}
},
mounted () {
bus.$on('move', pos => {
...
});
bus.$on('tick', payload => {
...
});
}
}
component2.vue
<template>
<div @mousemove.native="handleMove"></div>
</template>
<script>
export default {
methods: {
handleMove () {
bus.$emit('move');
}
},
mounted () {
bus.$on('click', () => {
...
})
}
}
用一张图来表示上述关系:
bus允许任意多的组件参与到这个事件环路内,他们共享同一组事件队列。
另外,如果事件关系很复杂,创建多个互不相关的bus也是可以的:
src
|-- bus
|-- clickBus.js
|-- moveBus.js
...
这里我们创建了多个bus,clickBus专门用来管理点击相关的事件,moveBus专门管理与移动相关的事件。需要注册或触发相应的事件时,引入对应的bus即可。
总结
一般来说,bus不应该被大规模用于项目中。因为bus内事件的注册和触发分别位于不同的组件内,不便于跟踪,这在一定程度上会带来调试上的困难。
从实现原理上来说,Vuex的store模式其实是bus模式的一种封装和变体,它也是借助一个额外的Vue实例实现的:
不同的是,bus模式专注于事件管理,而store模式专注于数据管理。
一般来说,Vue推荐开发者多关注业务逻辑(即数据),事件应该由store直接管理,而不是发生在两个不相关的组件之间。这也是为什么Vuex专注于数据管理,而不是事件管理。不过对我们来说,在恰当的时候使用bus模式,却有可能收到意想不到的效果。
更多推荐
所有评论(0)