vue组件之间通信(provide/inject与$attrs/$listeners) 之四
一. provide/inject
是Vue.js2.2.0版本后新增的API,虽然官方文档说,provide和inject主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中,但是在插件 / 组件库(比如 iView,事实上 iView 的很多组件都在用)。
这对选项需要一起使用,以允许一个祖先组件向其所有的子孙后代注入一个依赖,不论组件的层次有多深,并在起上下游关系成立的时间里始终生效。
provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
inject 选项应该是:一个字符串数组,或一个对象(属性值是一个对象时,包含from和default默认值)
注意:provide和inject绑定并不是可响应的。这显然不是设计的失误,而是刻意的。如果要详细了解,请参考:API — Vue.js
//祖先级组件(上级组件)
<template>
<div>
<Pro></Pro>
</div>
</template>
<script>
import Pro from '../components/provide.vue';
export default {
data(){
return{
}
},
provide:{
foo:'liuhua'
},
components:{
Pro,
}
}
</script>
<style scoped>
</style>
//子孙级组件(下级组件)
<template>
<div>
<p>{{foo}}</p>
</div>
</template>
<script>
export default {
data(){
return {
}
},
inject:['foo'],
}
</script>
<style scoped>
</style>
我们在上级组件中设置了一个provide:foo,值为liuhua,它的作用就是将foo这个变量提供给它的所有下级组件。而在下级组件中通过inject注入了从上级组件中提供的foo变量,那么在下级组件中,就可以直接通过this.foo来访问了
我们一般会在main.js中导入app.vue作为根组件,我们需要在app.vue上做文章,这就是我们实现功能的关键。我们可以这样理解:app.vue作为一个最外层的根组件,用来存储所有需要的全局数据和状态。因为项目中的所有组件(包含路由),它的父组件(或根组件)都是app.vue,所有我们可以把整个app.vue实例通过provide对外提供。那么,所有的组件都能共享其数据,方法等。
//app.vue,部分代码省略:
<script>
export default {
provide () {
return {
app: this
}
},
data () {
return {
userInfo: null
}
},
methods: {
getUserInfo () {
// 这里通过 ajax 获取用户信息后,赋值给 this.userInfo,以下为伪代码
$.ajax('/user/info', (data) => {
this.userInfo = data;
});
}
},
mounted () {
this.getUserInfo();
}
}
</script>
上面,我们把整个app.vue的实例`this`对外提供,接下来,任何组件(或路由)只要通过`inject`注入app.vue的话,都可以通过this.app.xxx的形式来访问app.vue的data,computed,method等内容
<template>
<div>
{{ app.userInfo }}
</div>
</template>
<script>
export default {
inject: ['app']
}
</script>
只要一个组件使用了 `provide` 向下提供数据,那其下所有的子组件都可以通过 `inject` 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,比如 `app`,那这个组件中就不能再声明 `app` 这个数据了,因为它已经被父级占有
我们介绍的这对 API,主要还是在独立组件中发挥作用
二.$attrs/$listeners
-
$attrs
是在vue的2.40版本以上添加的。 - 项目中有多层组件传参可以使用$attrs,可以使代码更加美观,更加简洁,维护代码的时候更方便。如果使用普通的父子组件传参prop和$emit,$on会很繁琐;如果使用vuex会大材小用,只是在这几个组件中使用,没必要使用vuex;使用事件总线eventBus,使用不恰当的话,有可能会出现事件多次执行。
非props属性:父组件传入子组件属性,但子组件没有接收称为非props属性,非props属性默认会加到子组件标签最外层(inheritAttrs:true,如果是false就不放到标签最外层显示)
所有的非props属性都可以通过$attrs收到
应用: v-bind="$attrs" 将所有的非props属性绑定到相应标签,也可以用于组件
所有组件上的方法绑定子组件都可以通过$listeners接收
应用: v-on="$listeners" 将所有的方法又绑定到组件相应标签,也可以用于组件
//最上层父级 传递数据的
<template>
<div>
<!-- 非props属性:组件标签上传入的属性如果子组件没有接收会跑到子组件标签最外层
-->
<!--通常: .native修饰符 才可以在组件标签上使用原生的事件 -->
<Son
src="https://img01.yzcdn.cn/vant/logo.png"
@click="sonClick"
@mouseleave="sonClick"
></Son>
</div>
</template>
<script>
import Son from "./components/son.vue";
export default {
components: {
Son,
},
methods: {
sonClick() {
console.log("sonClick");
},
},
};
</script>
子组件
<template>
<div>
<!-- 孙组件 通过该组件 接收祖先组件传递的src 与两个方法 -->
<SonSon v-bind="$attrs" v-on="$listeners"></SonSon>
<button @click="btnClick">打印$listeners</button>
<!-- 子组件 接收父组件传递的src 与两个方法 -->
<!-- <img v-bind="$attrs" v-on="$listeners" class="img" alt="" /> -->
{{ $attrs }}
<!-- <img :a='1' :b="2" class="img" alt="" /> -->
<!-- 等价于如下方式 -->
<!-- <img v-bind="obj" class="img" alt="" /> -->
</div>
</template>
<script>
import SonSon from "./sonson.vue";
export default {
components: {
SonSon,
},
data() {
return {
obj: {
a: 1,
b: 2,
},
};
},
// props: ["src"],//接收传递的值后,则$attrs失效
methods: {
btnClick() {
console.log(this.$listeners);
},
},
};
</script>
情况一:
直接在孙组件中用$attras接收祖先传递的 非props属性,用$listeners接收祖先的方法
<template>
<div>
<img v-bind="$attrs" alt="" />
<button v-on="$listeners">孙组件</button>
</div>
</template>
<script>
export default {
};
</script>
情况二:
儿子组件,中间层,作为父组件和孙子组件的传递中介,在儿子组件中给孙子组件添加v-bind="$attrs"
,这样孙子组件才能接收到数据,
在孙子组件中一定要使用props接收从父组件传递过来的数据,直接与prop传值一样使用在孙组件中