$attrs和 $listeners
第一种方式props和 $ emit处理父子组件之间的数据传输有一个问题:如果多层嵌套,父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?
如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。从Vue 2.4开始,提供了 $attrs和 $listeners来解决这个问题,能够让组件A之间传递消息给组件C。
1)$attrs
官方文档:包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=“$attrs” 传入内部组件——在创建高级别的组件时非常有用。
$ attrs只代表的是那些没有被声明为props的属性,如果某个prop被子组件中声明了(就是这个属性已经在子组件的props中了),在子组件中的 $ attr会把声明的prop剔除。
也就是说:一个组件在父组件中被引用,$attrs就是组件标签上的静态属性值(attr)和动态属性值(:attr)的对象集合,这个集合不包含class, style和事件属性。
// 父组件
<child-com class="com" name="attr" :foo="foo" :boo="boo" :coo="coo" :doo="doo" @click="test"></child-com>
...
data() {
return {
foo: 'Hello World!',
boo: 'Hello Javascript!',
coo: 'Hello Vue',
doo: 'Last'
}
},
...
// child-com.vue
export default {
name: 'childCom',
components: {
childCom2
},
created() {
console.log(this.$attrs) // {name: "attr", foo: "Hello World!", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
}
如果组件中声明了 $ prop,那么 $ attrs中和$prop中相同的属性会被移除。
<!-- child-com.vue -->
export default {
name: 'childCom',
props: ['foo'], // foo被声明
components: {
childCom2
},
created() {
console.log(this.$attrs) // {name: "attr", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
}
如果childCom里的子组件还用到foo,可以继续将foo传给子组件。
<template>
<div>
<p>foo: {{foo}} </p>
<child-com2 :foo="foo" v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = () => import('./childCom2.vue')
export default {
name: 'childCom1',
props: ['foo'], // foo作为props属性绑定
inheritAttrs: false,
components: {
childCom2
},
created() {
console.log(this.$attrs) // {name: "attr", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
}
</script>
<!-- childCom2.vue -->
created() {
console.log(this.$attrs) // {foo: "Hello World!", name: "attr", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
与 $ prop作比较,$ props是必须要在组件中注册props才能够拿到值,因此在嵌套层级比较深的组件中$attrs取值更加方便。
2)$listeners
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件——在创建更高层次的组件时非常有用。
$ listeners是组件中的内置属性,它的值是父组件(不含.native修饰器的)v-on事件监听器。
组件可以通过在自己的子组件上使用v-on=“$ listeners”,进一步把值传给自己的子组件。如果子组件已经绑定$listeners中同名的监听器,则两个监听器函数会以冒泡的方式先后执行。
父组件:
<template>
<div class="test">
<child v-bind="{name, sex, age}" v-on="{changeName,changeAge}"></child>
</div>
</template>
<script>
import child from './child'
export default {
data() {
return {
name: '张三',
sex: '男',
age: 11,
}
},
components: {
child
},
methods: {
changeName(name) {
this.name = name
},
changeAge(age) {
this.age = age
}
}
}
</script>
子组件child.vue
<template>
<div class="child">
child组件的$attrs {{$attrs}}
<child-child v-bind="$attrs" v-on="$listeners" @showAttrs="showAttrs"></child-child>
</div>
</template>
<script>
import childChild from './child-child'
export default {
name: "child",
props: ['name'],
inheritAttrs: false,
created() {
console.log('child', this.$listeners)
},
components: {
childChild
},
methods: {
showAttrs() {
console.log(this.$attrs)
}
}
}
</script>
孙子组件:child-child.vue
<template>
<div class="child-child">
child-child组件的$attrs {{$attrs}}
</div>
</template>
<script>
export default {
name: "child-child",
inheritAttrs: false,
created() {
console.log('child-child',this.$listeners)
}
}
</script>
打印结果: