在进行组件通信时,经常会遇到这样的需求:
父组件A给子组件B传值,B组件又继续传递给C,最终执行的是C。
也就是说,B只是中间起了传递作用,啥也没干。
如果是这样的需求,按父子组件的通信的规则,B得接收信息,并继续传递,对B组件来说是冗余;而用vuex呢?那更是大材小用杀鸡用牛刀了。
那咋办呢?如何能快速达到目的、而且代码不拖沓?
$attrs和$listeners就派上了用场。
一、$attrs
正常情况下,Vue推荐用props向子组件参数。但是在特定场景下,使用$attrs
会更方便。
$attrs其实相当于传输数据的一个桥梁,传递子组件中非prop定义的v-bind属性。
我们在使用组件的过程中,有时需要给被封装的子组件传递Prop,但是如果每一个Prop都需要父组件声明,再一个一个传递给子组件,那就太麻烦了。
此时我们可以利用组件实例上的$attrs属性来简化这个过程:
父组件.vue
<template>
<div>
<h2>attrs-listeners</h2>
<children :attrsValue="attrsValue" ></children>
</div>
</template>
<script>
import children from "./components/children.vue";
export default {
components: {
children,
},
data() {
return {
attrsValue: "muzidigbig",
};
},
};
</script>
子组件.vue
<template>
<div class="mystyle">
<p>attrs-listeners----子组件</p>
<hr>
<sub-children v-bind="$attrs" v-on="$listeners"></sub-children>
</div>
</template>
<script>
import subChildren from "./sub-children.vue";
export default {
components: {
subChildren,
},
data() {
return {};
},
created() {
// 接收父组件可能传递过来的 非prop
console.log(this.$attrs, this.$listeners);
},
};
</script>
孙组件.vue
<template>
<div class="mystyle">
<p>attrs-listeners----孙组件</p>
<p>孙传不传都可以---{{ attrsValue }}</p>
<input
type="text"
v-model="attrsValue"
/>
</div>
</template>
<script>
export default {
// inheritAttrs: false, // 解决:prop作为HTML属性绑定到组件的根元素上
// props: {
// attrsValue: {
// default: "muzi",
// },
// },
data() {
return {
attrsValue: "",
};
},
created() {
// 接收父组件可能传递过来的 非prop
console.log(this.$attrs);
this.attrsValue = this.$attrs.attrsValue;
},
};
</script>
查看打印结果:
这里面就包含的有我们传入的attrsValue属性,
但需要注意的一点是,Vue会将组件被传入,但未声明的prop作为HTML属性绑定到组件的根元素上:
因此我们需要用下面的属性来处理这种情况:
解决:
总结:
-
Vue 3.x中所有的属性,包括class/style都可以通过
$attrs
传递,这个与Vue2.x不同. -
v-bind="$attrs"
指令,能够指定dom节点,完成属性绑定。 -
$attrs
只适合传递原生属性/事件,其它的属性还是老老实实用props
和events
传递吧。
二、$listeners
定义:
包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
理解:
所谓$listeners其实就相当于一个中间件,当出现多级组件嵌套时,孙组件想传递数据给爷组件,那么就需要在父组件中给孙组件设置v-on="$listeners",然后通过@键的方式监听孙组件传递过来的数据。
为什么要用$listeners?
因为$listeners可以很好的解决在多级组件嵌套中,组件C传给组件A传递数据的问题。
刚才我们介绍了如何将prop传递给子组件,如果我们想将事件处理函数传递给子组件时。
原始做法:
我们先将一个事件处理函数绑定到子组件上
父组件.vue
<template>
<div>
<h2>attrs-listeners</h2>
<children :attrsValue="attrsValue" @handleInp="handleInp"></children>
</div>
</template>
<script>
import children from "./components/children.vue";
export default {
components: {
children,
},
data() {
return {
attrsValue: "muzidigbig",
};
},
methods: {
handleInp(params,event) {
console.log(params);
console.log("input",event);
},
},
};
</script>
子组件.vue
孙组件.vue
<template>
<div class="mystyle">
<p>attrs-listeners----孙组件</p>
<p>孙传不传都可以---{{ attrsValue }}</p>
<input
type="text"
v-model="attrsValue"
@input="$listeners.handleInp(attrsValue, $event)"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 解决:prop作为HTML属性绑定到组件的根元素上
// props: {
// attrsValue: {
// default: "muzi",
// },
// },
data() {
return {
attrsValue: "",
};
},
created() {
// 接收父组件可能传递过来的 非prop
console.log(this.$attrs, this.$listeners);
this.attrsValue = this.$attrs.attrsValue;
},
};
</script>
最终执行的是subChild.vue。
$attrs和$listeners的使用场景:
对一些UI库进行二次封装用,比如element-ui,里面的组件不能满足自己的使用场景的时候,会二次封装,但是又想保留他自己的属性和方法,那么这个时候时候$attrs和$listners是个完美的解决方案。
简单来说:$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。