前言
vue通信手段有很多种,props/emit、vuex、event bus、provide/inject等。还有一种通信方式,那就是 $attrs 和 $listeners,之前早就听说这两个api,趁着有空来补补。这种方式挺优雅,使用起来也不赖。下面例子都会通过父、子、孙子,三者的关系来说明使用方式。

$attrs
官方解释:
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
我的理解:
接收除了
props声明外的所有绑定属性(class、style除外)
图解:

由于child.vue 在 props 中声明了 name 属性,$attrs 中只有age、gender两个属性,输出结果为:
{ age: "20", gender: "man" }

上面打印结果为:
{height: '80', name: 'joe', gender: 'man'}//会把child页面的值,一起传过来
注:但若是child.vue页面有个prop:[“name”],则name不会传到grandson.vue页面中去,即:
{height: '80', gender: 'man'},如下:

另外可以在 grandson.vue 上通过 v-bind="$attrs",可以将属性继续向下传递,让 grandson.vue 也能访问到父组件的属性,这在传递多个属性时会显得很便捷,而不用一条条的进行绑定。
如果想要添加其他属性,可继续绑定属性。但要注意的是,继续绑定的属性和 $attrs 中的属性有重复时,继续绑定的属性优先级会更高。

打印结果为:
{height: '80', name: 'xth', gender: 'man'}//会把child页面的值,一起传过来,同时会以继续绑定的属性为最高优先级
$listeners
官方解释:
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
我的理解:
接收除了带有
.native事件修饰符的所有事件监听器
图解:

parent.vue 中对 child.vue 绑定了两个事件,带有.native的 click 事件和一个自定义事件,所以在 child.vue 中,输出 $listeners 的结果为:
{
customEvent: fn;
}

同 attrs 属性一样,可以通过 v-on="$listeners",将事件监听器继续向下传递,让 grandson.vue 访问到事件,且可以使用 $emit 触发 parent.vue 的函数。
如果想要添加其他事件监听器,可继续绑定事件。但要注意的是,继续绑定的事件和 $listeners 中的事件有重复时,不会被覆盖。当 grandson.vue 触发 customEvent 时,child.vue 和 parent.vue 的事件都会被触发,触发顺序类似于冒泡,先到 child.vue 再到 parent.vue。
以上转自:Vue - 组件通信之$attrs、$listeners
代码示例
1.$attrs 示例
<div id="app">
根组件
<hr />
<father :msg="msg" :rootName="name" :rootDetail="infor"></father>
</div>
<template id="father">
<div>
父组件---{{msg}}
<hr />
<son v-on="$listeners" v-bind="$attrs"></son>
</div>
</template>
<template id="son">
<div>子组件</div>
</template>
<script>
// 子组件
let son = {
template: "#son",
created() {
console.log(this.$attrs, "子组件,接受root的非prop的数据");
},
};
// 父组件
let father = {
template: "#father",
components: {
son,
},
props: ["msg"],
created() {
console.log(this.$attrs, "父组件,接受root的非prop的数据");
},
};
let vm = new Vue({
el: "#app",
components: {
father,
},
data() {
return {
msg: "根结点的数据",
name: "root",
infor: {
name: "xth",
age: 18,
address: "app根部",
},
};
},
});
</script>

2.$listeners 示例
<div id="app">
根组件
<hr />
<father @cli1="click1" @cli2="click2" @cli3.native="click3"></father>
</div>
<template id="father">
<div v-on="$listeners">
父组件 <button @click="$listeners.cli1">获取root方法cli1</button><br />
<hr />
<son v-on="$listeners"></son>
</div>
</template>
<template id="son">
<div>
子组件
<button @click="$listeners.cli2">获取root方法cli2</button>
</div>
</template>
<script>
// 子组件
let son = {
template: "#son",
created() {
console.log(this.$listeners, "子组件"); // 包含父级所有绑定的方法
},
};
// 父组件
let father = {
template: "#father",
components: {
son,
},
created() {
console.log(this.$listeners, "父组件,接受父级所有绑定的方法"); // 包含父级所有绑定的方法
},
};
let vm = new Vue({
el: "#app",
components: {
father,
},
methods: {
click1() {
console.log("点击事件cli1");
},
click2() {
console.log("点击事件cli2");
},
},
});
</script>

3.$attrs 、$listeners 、 prop 、 $emit 示例
<div id="app">
根组件
<hr />
<father
:msg="msg"
:rootName="name"
:rootDetail="infor"
@cli1="click1"
@cli2="click2"
@cli3.native="click3"
></father>
</div>
<template id="father">
<div v-on="$listeners">
父组件---{{msg}} <button @click="$listeners.cli1">获取root方法cli1</button><br />
<button @click="getRootMethod">获取root方法</button>
<hr />
<son v-on="$listeners" v-bind="$attrs" @fathermethod="fatherMethod"></son>
</div>
</template>
<template id="son">
<div>
子组件
<button @click="$listeners.cli2">获取root方法cli2</button>
<button @click="getFatherMethod">获取father方法</button>
</div>
</template>
<script>
// 子组件
let son = {
template: "#son",
created() {
console.log(this.$attrs, "子组件,接受root的非prop的数据");
console.log(this.$listeners, "子组件"); // 包含父级所有绑定的方法
},
methods: {
getFatherMethod() {
this.$emit("fathermethod");
},
},
};
// 父组件
let father = {
template: "#father",
components: {
son,
},
props: ["msg"],
created() {
console.log(this.$attrs, "父组件,接受root的非prop的数据");
console.log(this.$listeners, "父组件,接受父级所有绑定的方法"); // 包含父级所有绑定的方法
},
methods: {
getRootMethod() {
this.$parent.rootMethod();
},
fatherMethod() {
console.log("子组件的getFatherMethod方法点了我");
},
},
};
let vm = new Vue({
el: "#app",
components: {
father,
},
data() {
return {
msg: "根结点的数据",
name: "root",
infor: {
name: "xth",
age: 18,
address: "app根部",
},
};
},
methods: {
click1() {
console.log("点击事件cli1");
},
click2() {
console.log("点击事件cli2");
},
click3() {
console.log("点击事件cli3");
},
rootMethod() {
console.log("父组件的getRootMethod方法点了我");
},
},
});
</script>