Vue组件通信的常用方案
1.props
父组件向子组件传值(这个不必多说,不了解的朋友可以去Vue官网补下基础)
2.$emit
子组件向父组件传值(这个不必多说)
3.ref
(1)ref简介
ref可以用在dom元素和组件实例上,两种情况有些差别。应用在dom元素上获取的是真实的dom元素,应用在组件上获取的则是组件实例对象。
(2)示例代码
//父组件
<template>
<div>
<child ref="child"></child>
</div>
</template>
<script>
import child from "./child.vue";
export default {
methods: {
getList(){
this.$refs.child.init()
}
}
}
<script/>
//子组件
<template>
<div></div>
</template>
<script>
export default {
methods: {
init(){
console.log('获取列表')
}
}
}
<script/>
(3)弊端
-
破坏了单向数据流的原则
Vue中的单向数据流原则一定要弄清楚,简单来说就是数据应该是单向流动的,即从父组件向子组件流动。直接使用$ref在父组件修改子组件内的数据,这可能导致之后再维护和排错时候出现麻烦(可以细细体会)。 -
导致组件复用困难
使用$refs之后,只能把父子组件绑定在一块了。这违背了组件复用的某些原则,让组件复用的效果不是太好。
4.children
children的用法和ref用法很相似,有点不同的是children是一个数组,包含所有子组件对象。下面我写一个示例代码:
this.$children[0].func1(); // 我是子组件1
this.$children[1].func2(); //我是子组件2
5.parent
通过$parent,子组件可以调用父组件的方法或者使用子组件的数据(与ref类似)。
6.root
$root 属性指向 Vue 实例的根实例。在 Vue 组件树中,每个组件都可以通过 $root 访问到根实例,从而实现跨层级的通信和数据共享(不用多说)。
7.attrs和listeners
(1)作用
- attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
- listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
(2)使用场景
常常用于组件库二次封装。我举个例子:我要在原有的el-button第三方组件的基础上实现“hover提示并且点击按钮出现弹框”的功能,同时保证其原有的功能。
在父组件中:接收子组件,向子组件传递数据(参数)和定义自定义事件
//父组件
<template>
<div>
<h2>hover属性的按钮</h2>
<!-- 在使用封装的按钮时,向子组件传递相应的参数 -->
<!-- @click代表的是自定义事件 -->
<hintButton icon="el-icon-plus" size="mini" type="success" title="wanglm" @click="handler"></hintButton>
</div>
</template>
<script>
//引入子组件
import hintButton from '@/components/hintButton.vue'
export default {
name: 'app',
//注册
components: {
hintButton
},
methods: {
handler() {
alert('wanglm666')
}
}
}
</script>
<style></style>
在子组件中,通过v-bind接收父组件传递的数据,通过v-on可以获取到父组件给子组件传递自定义事件。
//子组件
<template>
<div>
<!-- 可以使用a标签实现按钮带有提示功能 -->
<a :title="title">
<el-button v-bind="$attrs" v-on="$listeners"></el-button>
</a>
</div>
</template>
<script>
export default {
name: 'hintButton',
props: ['title'],
mounted() {
console.log('this.$attrs', this.$attrs)
console.log('this.$listeners', this.$listeners)
}
}
</script>
<style></style>
在实际的组件库二次封装中,可能第三方组件会有很多参数,在这样子的嵌套结构中,经常用到上述方法巧妙地解决麻烦。
8.provide和inject
(1)作用
祖先组件通过provide传递数据给子孙组件,子孙组件通过inject接受数据。
(2)示例代码
//祖先组件
provide(){
return{
sendCount:this.count
}
}
//子孙组件
inject:['sendCount'],
和props用法有点类似。
9.全局事件总线
(1)安装总线
const vm = new Vue({
el: '#root',
...
beforeCreate() {
Vue.prototype.$eventbus = this;
//这里this是vue实例,它本身是已经实现了bus的功能(当然也实现了与bus无关的其它功能)
//也可自己写一个bus类
//Vue.prototype.$eventbus = new customBus();
}
})
(2)订阅数据
//接收数据的组件
mounted() {
this.$bus.$on('sendParams', function(params){
console.log('接收到的参数', params);
});
},
(3)发布数据
//发送数据的组件
<button @click="$bus.$emit('sendParams', 25)">发送数据</button>
10.vuex
(1)与事件总线有何不同
vuex是统一管理,事件总线不是集中管理,各组件都能emit,都能on接收,显得有点乱。
(2)原理图
我就不详细讲述vuex的原理及其使用了,不太熟悉的朋友可以参考vuex官网,其实内容也是比较简单的,后续可能考虑花一期把vuex讲透。
11.slot
插槽也是可以通信的。我就简单列举一下,插槽是个比较简单的概念。
(1) 默认插槽
(2) 具名插槽
//子组件
<template>
<slot> </slot>
<slot name="content"></slot>
</template>
//父组件
<child>
<template v-slot:default> </template>
<!-- 具名插槽用插槽名做参数-->
<template v-slot:content> ...</template>
</child>
(3) 作用域插槽
//子组件
<template>
<slot name="footer" testProps="111">
<h3>footer</h3>
</slot>
</template>
//父组件
<child>
<template v-slot:default="slotProps">
{{slotProps.testProps}}
</template>
<template #default="slotProps">
{{slotProps.testProps}}
</template>
</child>
12.第三方库
比如Pubsub,大家可以研究研究它的源码,里面用到了发布订阅模式。
(1) 安装
npm i pubsub-js
(2) 订阅数据
在接收数据的组件,绑定事件(订阅消息),留下回调,接收参数。
Pubsub.subscribe('changeMessage', this.changeMessage)
(3) 发布数据
在传递数据的组件,触发事件(发布消息),传递参数。
Pubsub.publish('changeMessage')
可以发现Pubsub和事件总线极其相似,唯一的区别在接收数据的函数中,参数一是消息类型。
13.手写Bus
(1) 定义Bus类
//自定义中央事件总线(Vue中是默认已经实现了$bus的功能)---下面的实现我是从Vue双向数据绑定底层原理那受到了启发
export class Bus {
constructor() {
this.callbacks = {}//存放事件,每个事件属性又都是一个对象,存放当前事件的所有回调
this.callbackEventId = 0;//每次自增
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || {};
this.callbacks[name][++this.callbackEventId] = fn;
return this.callbackEventId;
}
$emit(name, ...args) {
for (let id in this.callbacks[name]) {
this.callbacks[name][id](...args)
}
}
$off(name, id = undefined) {
if (id == undefined) {// 如果只提供了name参数,则只会移除该事件相关的监听器;
delete this.callbacks[name]
} else {// 如果同时提供了name和id 参数,则只会移除指定事件及回调函数的监听器。
delete this.callbacks[name][id]
if (Object.keys(this.callbacks[name]).length == 0) {
delete this.callbacks[name]
}
}
}
//清空所有的监听器
$offs() {
this.callbacks = {};
}
}
(2) 使用Bus类
const vm = new Vue({
el: '#root',
components: {
'wlm-student': student,
'wlm-school': school
},
data: {},
created() {
},
mounted() {
},
methods: {
},
beforeCreate() {
Vue.prototype.$eventbus = new Bus();
}
})