前言
- 组件的原则是只专注做一件事,且把这件事做好,我们可以把每个组件看成独立的个体,像人一样,但是单独的一个人做的事情再牛,他的力量也是有限的,所以我们才会有沟通,协作。
- 组件间的通信就像我们人与人说话一样,你告诉我你做了什么,我该做什么,这样才可以协同工作,通信尤为重要。
- 组件通信的本质就是让需要通信的两个组件可以说上话。
- 下面我们就来学习 Vue 组件间的通信吧~
- ps:
props和$emit等在 Vue组件 这里~
父子组件通信
- 除了
props和$emit外,我们还可以用别的方式去完成通信
$parent/$children
-
要说组件之间最简单粗暴的方式,那我觉得就是直接获取对应组件实例,不管是props还是方法我们都可以直接看到
-
父组件
<script>
import Child from "./Child";
export default {
components: {
Child,
},
mounted() {
// 获取子组件实例(数组)
console.log(this.$children);
// 直接修改子组件字段 vue会发出警告,不建议这样使用
this.$children[0].title = "jkjkl";
// 调用子组件方法
console.log(this.$children[0].getTitle());
},
};
</script>
- 子组件
<script>
export default {
props: ["title"],
mounted() {
// 获取父组件实例
console.log(this.$parent);
},
methods: {
getTitle() {
return this.title;
},
},
};
</script>
$parent和$children虽然粗暴,但是他对需要通信的组件绑定性强,你必须得知道你的子或父是什么,或者说你必须知道你的子组件或者父组件有什么东西的时候才可以调用。- 例如上面这个例子,假如我业务需求改变,
Child不是Parent的子组件,是他子组件的子组件,那么调用this.$children[0].getTitle()会报错,你调用不了不存在的东西。 - 我们一般还是使用
props和$emit,只要确定了 入口 和 出口即可,也使得组件间的耦合性降低。 ref和this.$refs的方式除了能获取真实的DOM元素,也可以获取子组件实例,使用在 Vue组件,这就不举例了
多层级组件通信
- 我们知道了怎么去进行父子之间的通信,那如果我们要沟通的组件并不是父子关系的情况怎么办呢?
- 例如:A>B>C C组件需要接收一个A传来的值,只用
props的话,我们就需要在B也同时接收以及传递给C,这不就很麻烦嘛,像接力还是像蜈蚣一样一个接一个,很恶心
provide/inject
- 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效
provide是注入依赖,inject来接收provide和inject主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中- 值得注意的是
provide和inject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的 - 例子结构为:CompA>CompB>CompC
CompA
<script>
import CompB from "./CompB";
export default {
// 写法一:Obj
// provide: {
// info: "any && !this"
// },
// 写法二:如果需要把 this 实例传给子孙级,需要用函数回调的方法注入
provide() {
return {
_this: this,
info: "any",
};
},
components: { CompB },
};
</script>
CompC
<script>
export default {
// inject 接收与 props 类似
// inject: ["info", "_this"],
// 可写为对象形式,接收两个参数
// from: property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
// default: property 是降级情况下使用的 value (默认值)
inject: {
info: {
from: "CompA",
default: "title-default",
},
_this: {},
},
props: ["title"],
mounted() {
console.log(this._this);
},
};
</script>
$attrs/$listeners
- vue 组件的通信有一个很有趣的行为,如果在祖先级传入的 attribute(class 和 style 除外),未经
props接收,他会传入组件内部
- 这个 attribute 会通过
$attrs获取 -- 在创建高级别的组件时非常有用 - 我们就可以同过
v-bind="$attrs"一层一层向下传递 - 例子结构为:CompA>CompB>CompC
CompA
<template>
<div>
CompA
<CompB title="my name is CompA"></CompB>
</div>
</template>
CompB
<template>
<div>
CompB
<CompC v-bind="$attrs"></CompC>
</div>
</template>
CompC
<script>
export default {
mounted() {
console.log(this.$attrs);
},
};
</script>
- 切记:如果需要用此方法传递信息,需要不被
props识别(且获取) - 现在我们还有个问题,他的 attribute 在页面结构中都暴露出来了,我不想让他被暴露出来怎么办呢
<script>
import CompC from "./CompC";
export default {
// 在接收的组件内 通过设置 inheritAttrs 为 false,可隐藏页面结构中的 attribute
inheritAttrs: false,
components: {
CompC,
},
};
</script>
$listeners与$attrs类似,不同的是$attrs传递的是信息,而$listeners传递的是方法- 例子结构为:CompA>CompB>CompC
CompA
<template>
<div>
CompA
<!-- 将 handleClick 的点击事件往下传 -->
<!-- click 为 key,handleClick 为 value, -->
<CompB @click="handleClick"></CompB>
</div>
</template>
<script>
import CompB from "./CompB";
export default {
methods: {
handleClick() {
console.log("handleClick");
},
},
components: {
CompB,
},
};
</script>
CompB
<template>
<div>
CompB
<!-- 事件由 v-on 继续往下传递 -->
<CompC v-on="$listeners"></CompC>
</div>
</template>
CompC
<script>
export default {
mounted() {
console.log(this.$listeners); // {click: ƒ}
},
};
</script>
- 又或者说
$attrs和$listeners都是收集东西的盒子,一个是收集信息,一个是收集方法
非关系组件通信
vm.$on/vm.$emit/vm.$once/vm.$off
- 上面的方法都只适用于父子孙关系的组件,那么如果是兄弟组件,甚至是非关系组件,又或是我懒得找他们关系了,他们该怎么通信呢?
- 像我们经常跟朋友聊天,但是我们不常有空出来见面,怎么办?微信群聊!借助一个中间媒介帮助我们聊天说话。而非关系组件通信也是同理,只是我们微信群聊是借助了网络和微信,而非关系组件借助的是一个 Vue 实例。
- 这里涉及一个 发布订阅 模式,简单来说就是订报纸,不同的人向报社订阅报纸,每天新闻出来了,报社就会向我们已经订阅的人派发报纸。
- 通过
vm.$on或vm.$once进行订阅,vm.$emit发布,vm.off取消订阅 - 以下例子 CompA、CompB、CompC 层级随便(不牵涉层级)
借助了一个 Vue 实例,所以我们新建一个 eventBus.js 导出
import Vue from 'vue'
// 就这么简单,导出一个实例即可
export const EventBus = new Vue();
CompA
<script>
import { EventBus } from "../eventBus";
export default {
mounted() {
// 利用导出的实例,使用方法 $on 订阅 handle 事件,当 handle 触发时,执行回调
EventBus.$on("handle", (msg) => {
console.log(msg + " -- A");
});
},
};
</script>
CompB
<script>
import { EventBus } from "../eventBus";
export default {
mounted() {
// $once 与 $on 一样都是订阅,不同的是 $once 只会触发一次
EventBus.$once("handle", (msg) => {
console.log(msg + " -- B");
});
},
};
</script>
CompC
<template>
<div>
CompC
<button @click="handleClick">发布</button>
<button @click="handleOff">off</button>
</div>
</template>
<script>
import { EventBus } from "../eventBus";
export default {
mounted() {
// this.handleOn 作为句柄订阅 handle
EventBus.$on("handle", this.handleOn);
},
methods: {
handleClick() {
// 利用导出的实例,使用方法 $emit 发布消息,触发 handle 事件
EventBus.$emit("handle", "谁接收到了");
},
// 方法句柄
handleOn(msg) {
console.log(msg + " -- C");
},
handleOff() {
console.log("---- off C ----");
// 利用导出的实例,使用方法 $off 取消订阅
// 此处为取消订阅 handle 事件 handleOn 方法
EventBus.$off("handle", this.handleOn);
},
},
};
</script>
- 我们依次触发
btn发布 -> 发布,很清楚的可以看到$on会一直被触发,而$once只触发了一次就么的了 - 继续触发
btnoff -> 发布,可以看到 C 也没再触发了,他已经取消了订阅 - 其实熟悉
jQ的朋友一定对于$on和$off感觉很眼熟,我们可以类比$(el).on()和$(el).off(),写法都是差不多的,也可以类比addeventlistener和removeeventlistener监听和移除事件去理解、去记住 - 跟
removeeventlistener和$(el).off()略有不同,$off会根据参数的不同,触发不同的效果- 如果没有提供参数,则移除所有的事件监听器
- 如果只提供了事件,则移除该事件所有的监听器
- 如果同时提供了事件与回调,则只移除这个回调的监听器
没有结束语~加油!