在vue中, 组件的关系不外乎以下三种:
组件是需要通信的,在开发中,常用到的通信方式有:vuex、eventBus、以及props与emit、$parent与$children,除此之外,还有provide与inject、$attrs与$listeners等。
这个相信大家用的很多了,简单回顾一下:
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
这个称为‘事件总线’,简单看下是怎么使用的:
这个不用多说了,父子通信用的最多的应该就是这个了。当然,如果以子组件为跳板,也可以做到祖孙之间通信,不过比较麻烦。不建议这样操作。
$parent直接访问的就是父实例,而$children则返回的是实例数组。所以我一般都是$parent搭配$refs使用。
这两个可能会用的比较少,来看下官网的介绍:
怎么理解呢,简单来讲就是,$attrs接收除了prop、style、class之外的所有绑定属性,$listeners则接收除了被.native修饰的所有绑定事件。具体来看下例子:
<template>
<div>
<p>父组件</p>
<input type="text" v-model="formData.inputValue" />
<p>子组件</p>
<Son
:inputValue="formData.inputValue"
:otherValue="otherValue"
@success="success"
@input.native="handleInput"
v-bind="$attrs"
v-on="$listeners"
></Son>
</div>
</template>
<script>
import Son from "./son.vue";
export default {
components: { Son },
provide() {
return {
father: this.formData,
};
},
data() {
return {
formData: {
inputValue: "123",
},
otherValue: 999,
};
},
methods: {
success(data) {
console.log(data);
},
handleInput() {},
},
};
</script>
<template>
<div>
<input type="text" v-model="inputValue" @change="handleChange" />
</div>
</template>
<script>
export default {
props: {
inputValue: String,
},
created() {
console.log(this.$attrs, "son---$attrs");
console.log(this.$listeners, "son---$listeners");
},
methods: {
handleChange() {
this.father.inputValue = this.inputValue;
},
},
};
</script>
按照之前的理解,$attrs应该只能接收到otherValue,$listeners则只能接收到success事件,看下打印结果:
结果确实也是这样的。除此之外,还可传递给孙组件:
<template>
<div>
<input type="text" v-model="inputValue" @change="handleChange" />
<GrandSon v-bind="$attrs" v-on="$listeners"></GrandSon>
</div>
</template>
<script>
import GrandSon from "./grandSon.vue";
export default {
components: { GrandSon },
props: {
inputValue: String,
},
created() {
console.log(this.$attrs, "son---$attrs");
console.log(this.$listeners, "son---$listeners");
},
methods: {
handleChange() {
this.father.inputValue = this.inputValue;
},
},
};
</script>
<template>
<div>
<input type="text" v-model="inputValue" @change="handleChange" />
</div>
</template>
<script>
export default {
props: {
inputValue: String,
},
created() {
console.log(this.$attrs, "grandSon---$attrs");
console.log(this.$listeners, "grandSon---$listeners");
},
methods: {
handleChange() {
this.father.inputValue = this.inputValue;
},
},
};
</script>
provide/inject可以在一个祖先组件中向它的所有后辈组件注入一个依赖,只要上下游关系成立就能生效。简单的理解就是provide是注入数据,inject是获取数据。所以provide是用于父组件,inject是用于子孙组件。provide应该是一个对象或者返回一个对象的函数,inject应该是一个字符串数组或者一个对象。官网提到这么一句话:
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
这句话怎么理解呢?字面理解就是你要想在上下游传递的那个数据是可响应的,那么就应该以对象的形式传递,先试一下以基本数据类型的形式传递,看下例子:
父组件:
<template>
<div>
<p>父组件</p>
<input type="text" v-model="inputValue" />
<p>子组件</p>
<Son></Son>
<p>孙组件</p>
<GrandSon></GrandSon>
</div>
</template>
<script>
import Son from "./son.vue";
import GrandSon from "./grandSon.vue";
export default {
components: { Son, GrandSon },
provide() {
return {
father: this.inputValue,
};
},
data() {
return {
inputValue: "123",
};
},
};
</script>
子组件:
<template>
<div>
<input type="text" v-model="inputValue" @change="handleChange" />
</div>
</template>
<script>
export default {
inject: ["father"],
data() {
return {
inputValue: "",
};
},
watch: {
father(val) {
console.log(val, "val");
this.inputValue = val;
},
},
created() {
console.log(this, "this");
},
methods: {
handleChange() {
this.father.inputValue = this.inputValue;
},
},
};
</script>
在子组件打印this:
可以看到,父组件的inputValue值是被注入到子组件当中的。但却监听不到这个father。
然后,我们改成以对象的形式进行注入:
<template>
<div>
<p>父组件</p>
<input type="text" v-model="formData.inputValue" />
<p>子组件</p>
<Son></Son>
<p>孙组件</p>
<GrandSon></GrandSon>
</div>
</template>
<script>
import Son from "./son.vue";
import GrandSon from "./grandSon.vue";
export default {
components: { Son, GrandSon },
provide() {
return {
father: this.formData,
};
},
data() {
return {
formData: {
inputValue: "123",
},
};
},
};
</script>
<template>
<div>
<input type="text" v-model="inputValue" @change="handleChange" />
</div>
</template>
<script>
export default {
inject: ["father"],
data() {
return {
inputValue: "",
};
},
watch: {
'father.inputValue'(val){
console.log(val, "val");
this.inputValue = val;
},
},
created() {
console.log(this, "this");
},
methods: {
handleChange() {
this.father.inputValue = this.inputValue;
},
},
};
</script>
这个时候我们看下打印的this以及效果:
这样就可以实现数据的响应了。这里有一个点需要注意,如果在父组件中将整个父组件的this注入到后代组件中,在后代组件中是不能通过深度监听来监听这个注入的对象的,会报堆栈溢出的错误。所以这里我用的是this.formData
的形式注入。这样在子孙组件中可以通过'father.inputValue'
这样的形式监听,也可以通过这样的形式:
father: {
handler(val) {
console.log(val);
},
deep: true,
},
至于为什么会导致这个问题,我们先看下深度监听的实现方式:
这段注释什么意思呢,简单理解就是vue是通过递归遍历对象里面的每一个属性,将是对象的属性收集起来进行监听。众所周知,递归是很容易引起堆栈溢出的,而看下this对象就不难理解为什么会导致堆栈溢出了(太多了,而且是层层嵌套下去的)。
以上就是Vue组件通信的几种方式,如果还要在扯一扯,浏览器的缓存也可以作为一种手段。。。