我们知道,向子组件传递数据,是通过 v-bind 子类组件定义的 props 属性完成的,这只适用于单向、两层组件之间。同样地,事件传递也是在父组件中用 v-on 给子组件绑定事件,然后在子组件中通过 this.$emit
触发的、以达到修改父组件数据的目的。
那么,在多层嵌套组件中,顶层组件和最底层组件之间如何进行数据传递和事件触发呢?比如,A 组件引用了 B 组件,而 B 组件又引用了 C 组件,那么怎么在 A 中将数据传给 C ;在 C 中,怎么触发 A 中的方法呢?这就是 $attrs
和 $listeners
的作用了。
1.$attrs
继承所有的父组件属性(没有通过 props 接收的属性还有 class 类名 和 style 样式 )。
javascript
// parent.vue
<m-child :data-status="dataStatus" :data-message="dataMessage"></m-child>
export default {
data(){
return {
dataStatus: '123',
dataMessage: '456'
}
}
}
// m-child.vue
<m-grandron v-bind="$attrs" data-message="789"></m-grandron>
export default {
props:{
dataStatus: String,
},
created(){
console.log('dataStatus',this.dataStatus) // 123
console.log('dataMessage',this.dataStatus) // 456
}
}
// m-grandron.vue
export default {
created(){
console.log('dataStatus',this.$attrs)
// 因为child组件中props声名了dataStatus所以这里$attrs就不含有dataStatus
// 因为传的dataMessage和$attrs内同名,所以值被覆盖变为789
}
}
如果我们按常规的props传值,就要一层一层传,层级多了就很恶心,使用$attrs就很轻松的做到了。
2.$listeners
它是一个对象,能接收所有的方法绑定,里面包含了作用在这个组件上的所有监听器,配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。
有一点不同的是:当出现同名的事件时,不会被覆盖,而是都会执行,执行顺序就是事件冒泡的执行顺序,先触发child 再触发parent\
typescript
// parent.vue
<m-child @customEvent="ev_customEvent"></m-child>
export default {
data(){
return {
dataStatus: '123',
dataMessage: '456'
}
},
methods:{
ev_customEvent(){
console.log('my name is parent!')
}
}
}
// m-child.vue
<m-grandron v-on="$listeners" @customEvent="ev_customEvent"></m-grandron>
export default {
props:{
dataStatus: String,
},
data(){
return {}
},
methods:{
ev_customEvent(){
console.log('my name is child!')
}
}
}
// m-grandron.vue
<button @click="$emit('customEvent')">click me!</button>
export default {
data(){
return {}
},
}
// 控制台
// my name is child!
// my name is parent!
使用$listeners 属性可以很方便的在层级很深的组件内,立马就修改最外层的父组件内的属性值。
注意:在vue3.0 中$listeners被移除了,将所有属性都集合到attrs里面了